protected override void ClientUpdate(ClientUpdateData data)
        {
            var character   = data.GameObject;
            var clientState = data.ClientState;
            var publicState = data.PublicState;

            if (!character.IsCurrentClientCharacter)
            {
                ClientCharacterEquipmentHelper.ClientRefreshEquipment(character, clientState, publicState);
                base.ClientUpdate(data);
                return;
            }

            // next code is only for the current client character
            var privateState = data.PrivateState;

            if (!publicState.IsDead)
            {
                SharedRefreshSelectedHotbarItem(character, privateState);
            }

            ClientCharacterEquipmentHelper.ClientRefreshEquipment(character, clientState, publicState);

            this.ServerRebuildFinalCacheIfNeeded(privateState, publicState);
            this.SharedApplyInput(character, privateState, publicState);

            ClientCharacterAnimationHelper.ClientUpdateAnimation(
                character,
                clientState,
                publicState);

            if (publicState.IsDead)
            {
                // dead - stops processing character
                WindowRespawn.EnsureOpened();
                return;
            }

            // character is alive - can process its actions
            WindowRespawn.EnsureClosed();
            // update weapon state (fires the weapon if needed)
            WeaponSystem.SharedUpdateCurrentWeapon(character, privateState.WeaponState, data.DeltaTime);
            // update current action state (if any)
            privateState.CurrentActionState?.SharedUpdate(data.DeltaTime);
            // consumes/restores stamina
            CharacterStaminaSystem.SharedUpdate(character, publicState, privateState, data.DeltaTime);
        }
        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;
                }
            }
        }