public static void ClientRefreshEquipment(
            ICharacter character,
            BaseCharacterClientState clientState,
            ICharacterPublicStateWithEquipment publicState)
        {
            var selectedItem = character.IsCurrentClientCharacter
                                   ? ClientHotbarSelectedItemManager.SelectedItem
                                   : publicState.SelectedHotbarItem;

            var containerEquipment = publicState.ContainerEquipment;

            if (clientState.LastEquipmentContainerHash == containerEquipment.StateHash &&
                clientState.SkeletonRenderer != null &&
                clientState.LastSelectedItem == selectedItem)
            {
                return;
            }

            clientState.LastEquipmentContainerHash = containerEquipment.StateHash;
            ClientRebuildAppearance(
                character,
                clientState,
                publicState,
                selectedItem);
        }
        public static void ClientRebuildAppearance(
            ICharacter character,
            BaseCharacterClientState clientState,
            ICharacterPublicState publicState,
            IItem selectedItem)
        {
            if (!character.IsNpc)
            {
                var vehicle = character.SharedGetCurrentVehicle();
                if (vehicle is not null &&
                    !vehicle.IsInitialized)
                {
                    // Character has a vehicle which is not yet initialized.
                    // Don't build a skeleton for it now.
                    // When vehicle will be initialized,
                    // it will automatically re-initialize the character and invoke this method.
                    return;
                }
            }

            IProtoCharacterSkeleton newProtoSkeleton = null;
            double skeletonScaleMultiplier           = 0;

            if (publicState is PlayerCharacterPublicState playerCharacterPublicState &&
                playerCharacterPublicState.CurrentVehicle is not null &&
                playerCharacterPublicState.CurrentVehicle.IsInitialized)
            {
                var protoVehicle = (IProtoVehicle)playerCharacterPublicState.CurrentVehicle.ProtoWorldObject;
                protoVehicle.SharedGetSkeletonProto(null,
                                                    out var protoSkeletonVehicle,
                                                    out var scaleResult);
                if (protoSkeletonVehicle is not null)
                {
                    newProtoSkeleton        = (ProtoCharacterSkeleton)protoSkeletonVehicle;
                    skeletonScaleMultiplier = scaleResult;
                }
            }

            if (newProtoSkeleton is null)
            {
                character.ProtoCharacter.SharedGetSkeletonProto(
                    character,
                    out newProtoSkeleton,
                    out skeletonScaleMultiplier);
            }

            var isSkeletonChanged = newProtoSkeleton != clientState.CurrentProtoSkeleton;

            clientState.CurrentProtoSkeleton = (ProtoCharacterSkeleton)newProtoSkeleton;
            clientState.CurrentProtoSkeletonScaleMultiplier = skeletonScaleMultiplier;

            var skeletonRenderer   = clientState.SkeletonRenderer;
            var skeletonComponents = clientState.SkeletonComponents;

            if (skeletonComponents.Count > 0)
            {
                foreach (var comp in skeletonComponents)
                {
                    try
                    {
                        comp.Destroy();
                    }
                    catch (Exception ex)
                    {
                        Api.Logger.Exception(ex);
                    }
                }

                skeletonComponents.Clear();
            }

            var isNewSkeleton = false;

            // create shadow renderer
            clientState.RendererShadow?.Destroy();
            clientState.RendererShadow = ((ProtoCharacterSkeleton)newProtoSkeleton)
                                         .ClientCreateShadowRenderer(character,
                                                                     skeletonScaleMultiplier);

            if (skeletonRenderer is null ||
                isSkeletonChanged)
            {
                skeletonRenderer?.Destroy();
                if (newProtoSkeleton is null ||
                    newProtoSkeleton.SkeletonResourceFront is null)
                {
                    return;
                }

                var scale = clientState.CurrentProtoSkeleton.WorldScale
                            * clientState.CurrentProtoSkeletonScaleMultiplier;

                // create new skeleton renderer
                skeletonRenderer = CreateCharacterSkeleton(character.ClientSceneObject, newProtoSkeleton, scale);

                clientState.SkeletonRenderer = skeletonRenderer;
                isNewSkeleton = true;
                //Api.Logger.Write("Skeleton created for " + character);
            }

            if (clientState.LastSelectedItem != selectedItem)
            {
                clientState.LastSelectedItem = selectedItem;
                if (!isNewSkeleton)
                {
                    // cleanup skeleton
                    skeletonRenderer.ResetSkeleton();
                }
            }

            var containerEquipment = (publicState as ICharacterPublicStateWithEquipment)
                                     ?.ContainerEquipment;

            if (containerEquipment is not null)
            {
                SetupSkeletonEquipmentForCharacter(
                    character,
                    containerEquipment,
                    skeletonRenderer,
                    clientState.CurrentProtoSkeleton,
                    skeletonComponents);
            }

            if (!character.IsNpc)
            {
                var vehicle = character.SharedGetCurrentVehicle();
                if (vehicle is not null &&
                    vehicle.IsInitialized)
                {
                    var protoVehicle = (IProtoVehicle)vehicle.ProtoGameObject;
                    protoVehicle.ClientSetupSkeleton(vehicle,
                                                     newProtoSkeleton,
                                                     skeletonRenderer,
                                                     skeletonComponents);
                }
            }

            if (selectedItem is not null)
            {
                var activeProtoItem = selectedItem.ProtoGameObject as IProtoItemWithCharacterAppearance;
                activeProtoItem?.ClientSetupSkeleton(selectedItem,
                                                     character,
                                                     clientState.CurrentProtoSkeleton,
                                                     skeletonRenderer,
                                                     skeletonComponents);
            }

            if (character.IsCurrentClientCharacter &&
                character.ProtoCharacter is PlayerCharacter)
            {
                TryAddArtificialLightArea(character, skeletonComponents);
            }

            // ensure all the added skeleton components are instantly updated
            // to make them ready for rendering (fixes light flickering issue)
            foreach (var c in skeletonComponents)
            {
                if (!(c is ClientComponent component) ||
                    !component.IsEnabled)
                {
                    continue;
                }

                component.Update(0);

                if (component.IsLateUpdateEnabled)
                {
                    component.LateUpdate(0);
                }
            }
        }
        public static void ClientRebuildAppearance(
            ICharacter character,
            BaseCharacterClientState clientState,
            ICharacterPublicState publicState,
            IItem selectedItem)
        {
            character.ProtoCharacter.SharedGetSkeletonProto(
                character,
                out var newProtoSkeleton,
                out var skeletonScaleMultiplier);

            var isSkeletonChanged = newProtoSkeleton != clientState.CurrentProtoSkeleton;

            clientState.CurrentProtoSkeleton = (ProtoCharacterSkeleton)newProtoSkeleton;
            clientState.CurrentProtoSkeletonScaleMultiplier = skeletonScaleMultiplier;

            var skeletonRenderer   = clientState.SkeletonRenderer;
            var skeletonComponents = clientState.SkeletonComponents;

            if (skeletonComponents.Count > 0)
            {
                foreach (var comp in skeletonComponents)
                {
                    try
                    {
                        comp.Destroy();
                    }
                    catch (Exception ex)
                    {
                        Api.Logger.Exception(ex);
                    }
                }

                skeletonComponents.Clear();
            }

            var isNewSkeleton = false;

            if (skeletonRenderer == null ||
                isSkeletonChanged)
            {
                skeletonRenderer?.Destroy();
                if (newProtoSkeleton == null ||
                    newProtoSkeleton.SkeletonResourceFront == null)
                {
                    return;
                }

                var sceneObject = Api.Client.Scene.GetSceneObject(character);
                var scale       = clientState.CurrentProtoSkeleton.WorldScale
                                  * clientState.CurrentProtoSkeletonScaleMultiplier;

                // create new skeleton renderer
                skeletonRenderer = CreateCharacterSkeleton(sceneObject, newProtoSkeleton, scale);

                clientState.SkeletonRenderer = skeletonRenderer;
                isNewSkeleton = true;
                //Api.Logger.Write("Skeleton created for " + character);
            }

            if (clientState.LastSelectedItem != selectedItem)
            {
                clientState.LastSelectedItem = selectedItem;
                if (!isNewSkeleton)
                {
                    // cleanup skeleton
                    skeletonRenderer.ResetSkeleton();
                }
            }

            var containerEquipment = (publicState as ICharacterPublicStateWithEquipment)
                                     ?.ContainerEquipment;

            if (containerEquipment != null)
            {
                SetupSkeletonEquipmentForCharacter(
                    character,
                    containerEquipment,
                    skeletonRenderer,
                    clientState.CurrentProtoSkeleton,
                    skeletonComponents);
            }

            if (selectedItem != null)
            {
                var activeProtoItem = selectedItem.ProtoGameObject as IProtoItemWithCharacterAppearance;
                activeProtoItem?.ClientSetupSkeleton(selectedItem, character, skeletonRenderer, skeletonComponents);
            }

            if (character.IsCurrentClientCharacter &&
                character.ProtoCharacter is PlayerCharacter &&
                !skeletonComponents.Any(c => c is ClientComponentLightInSkeleton))
            {
                // this is current client character and it doesn't have light in hand
                // create a faint light source (see called method comments)
                var lightSource = PlayerCharacter.ClientCreateDefaultLightSource(character);
                if (lightSource != null)
                {
                    skeletonComponents.Add(lightSource);
                }
            }
        }