示例#1
0
        public static void RefreshCharacterFinalStatsCache(
            IReadOnlyStatsDictionary protoEffects,
            ICharacterPublicState publicState,
            BaseCharacterPrivateState privateState,
            bool isFirstTime = false)
        {
            var containerEquipment = (publicState as ICharacterPublicStateWithEquipment)?.ContainerEquipment;

            privateState.ContainerEquipmentLastStateHash = containerEquipment?.StateHash;

            FinalStatsCache finalStatsCache;

            using (var tempStatsCache = TempStatsCache.GetFromPool(isMultipliersSummed: false))
            {
                // merge character prototype effects
                tempStatsCache.Merge(protoEffects);

                if (privateState is PlayerCharacterPrivateState playerCharacterPrivateState)
                {
                    // merge skill effects
                    var skills = playerCharacterPrivateState.Skills;
                    skills.SharedFillEffectsCache(tempStatsCache);
                }

                foreach (var statusEffect in privateState.StatusEffects)
                {
                    var protoStatusEffect = (IProtoStatusEffect)statusEffect.ProtoLogicObject;
                    tempStatsCache.Merge(protoStatusEffect.ProtoEffects);
                }

                if (containerEquipment != null)
                {
                    // merge equipment effects
                    foreach (var item in containerEquipment.Items)
                    {
                        if (item.ProtoGameObject is IProtoItemEquipment protoEquipment)
                        {
                            tempStatsCache.Merge(protoEquipment.ProtoEffects);
                        }
                    }
                }

                // calculate the final stats cache
                finalStatsCache = tempStatsCache.CalculateFinalStatsCache();
            }

            privateState.FinalStatsCache = finalStatsCache;

            // need to recalculate the weapon cache as it depends on the final cache
            privateState.WeaponState.WeaponCache = null;

            ApplyFinalStatsCache(finalStatsCache, publicState.CurrentStats, isFirstTime);
        }
示例#2
0
        public ViewModelCharacterBossHealthbarControl(ICharacter character)
        {
            this.character   = character;
            this.publicState = character.GetPublicState <ICharacterPublicState>();
            this.clientState = character.GetClientState <BaseCharacterClientState>();

            this.ViewModelCharacterHealthBarControl = new ViewModelCharacterHealthBarControl
            {
                CharacterCurrentStats = this.publicState.CurrentStats
            };

            ClientUpdateHelper.UpdateCallback += this.Update;
        }
示例#3
0
        public void Setup(IComponentSkeleton skeleton, ICharacter characterPilot)
        {
            this.skeleton             = skeleton;
            this.characterPublicState = characterPilot.GetPublicState <ICharacterPublicState>();

            this.soundEmitter = Api.Client.Audio.CreateSoundEmitter(this.SceneObject,
                                                                    SoundResource,
                                                                    is3D: !characterPilot.IsCurrentClientCharacter,
                                                                    isLooped: true,
                                                                    isPlaying: true,
                                                                    volume: 0);
            this.lastAngleRad = this.GetCurrentAngleRad();
        }
示例#4
0
        private void ServerRebuildFinalCacheIfNeeded(
            ICharacter character,
            BaseCharacterPrivateState privateState,
            ICharacterPublicState publicState)
        {
            if (!privateState.FinalStatsCache.IsDirty)
            {
                return;
            }

            // rebuild stats cache
            SharedCharacterStatsHelper.RefreshCharacterFinalStatsCache(this.ProtoCharacterDefaultEffects,
                                                                       publicState,
                                                                       privateState);
        }
示例#5
0
        public static double SharedGetCurrentRangeMax(
            ICharacterPublicState characterPublicState)
        {
            if (!(characterPublicState.SelectedItemWeaponProto is IProtoItemWeaponRanged protoWeaponRanged))
            {
                return(0);
            }

            var damageDescription = WeaponSystem.GetCurrentDamageDescription(characterPublicState.SelectedItem,
                                                                             protoWeaponRanged,
                                                                             out _);

            return(damageDescription is null
                       ? 0
                       : damageDescription.RangeMax *protoWeaponRanged.RangeMultiplier);
        }
示例#6
0
        public static void ClientUpdateAnimation(
            ICharacter character,
            BaseCharacterClientState clientState,
            ICharacterPublicState publicState)
        {
            var skeletonRenderer = clientState.SkeletonRenderer;

            if (skeletonRenderer is null)
            {
                return;
            }

            var activeWeaponProto = publicState.SelectedItemWeaponProto;

            var protoSkeleton  = clientState.CurrentProtoSkeleton;
            var rendererShadow = clientState.RendererShadow;

            if (publicState is PlayerCharacterPublicState
            {
                CurrentPublicActionState : CharacterLaunchpadEscapeAction.PublicState
            })
示例#7
0
        public ViewModelCharacterOverlayControl(ICharacter character)
        {
            this.character   = character;
            this.publicState = character.GetPublicState <ICharacterPublicState>();

            if (!character.IsNpc)
            {
                this.ViewModelCharacterNameControl = new ViewModelCharacterNameControl(character);
            }

            this.ViewModelCharacterUnstuckInfoControl = new ViewModelCharacterUnstuckInfoControl(character);

            this.ViewModelCharacterHealthBarControl = new ViewModelCharacterHealthBarControl();
            this.ViewModelCharacterHealthBarControl.CharacterCurrentStats = this.publicState.CurrentStats;

            this.ViewModelCharacterPublicStatusEffects =
                new ViewModelCharacterPublicStatusEffects(this.publicState.CurrentPublicStatusEffects);

            ClientUpdateHelper.UpdateCallback += this.Update;
            this.Update();
        }
示例#8
0
        public ViewModelCharacterOverlayControl(ICharacter character, Action callbackVisualStateChanged)
        {
            this.character = character;
            this.callbackVisualStateChanged = callbackVisualStateChanged;
            this.publicState = character.GetPublicState <ICharacterPublicState>();

            if (!character.IsNpc)
            {
                this.ViewModelCharacterNameControl = new ViewModelCharacterNameControl(character);
            }

            this.ViewModelCharacterUnstuckInfoControl = new ViewModelCharacterUnstuckInfoControl(character);

            this.ViewModelCharacterHealthBarControl = new ViewModelCharacterHealthBarControl();
            this.ViewModelCharacterHealthBarControl.CharacterCurrentStats = this.publicState.CurrentStats;
            this.ViewModelCharacterHealthBarControl.MobPublicState        = this.publicState as CharacterMobPublicState; //MOD

            this.ViewModelCharacterPublicStatusEffects = new ViewModelCharacterPublicStatusEffects(
                this.publicState.CurrentPublicStatusEffects);

            ClientUpdateHelper.UpdateCallback += this.Update;

            this.visualStateName = this.GetDesiredVisualStateName();
        }
        public static void ClientUpdateAnimation(
            ICharacter character,
            BaseCharacterClientState clientState,
            ICharacterPublicState publicState)
        {
            var skeletonRenderer = clientState.SkeletonRenderer;

            if (skeletonRenderer is null)
            {
                return;
            }

            var activeWeaponProto = publicState.SelectedItemWeaponProto;

            var protoSkeleton  = clientState.CurrentProtoSkeleton;
            var rendererShadow = clientState.RendererShadow;
            var wasDead        = clientState.IsDead;

            clientState.IsDead = publicState.IsDead;

            if (publicState.IsDead)
            {
                if (skeletonRenderer.GetCurrentAnimationName(AnimationTrackIndexes.Primary)
                    == "Death")
                {
                    // already in death animation
                    return;
                }

                // character was not dead on client and now become dead
                skeletonRenderer.ResetSkeleton();

                skeletonRenderer.SelectCurrentSkeleton(
                    protoSkeleton.SkeletonResourceFront,
                    "Idle",
                    isLooped: false);

                skeletonRenderer.SetAnimation(AnimationTrackIndexes.Primary, "Death", isLooped: false);

                if (!wasDead.HasValue)
                {
                    // character entered scope and it's dead
                    skeletonRenderer.SetAnimationTime(0, 10000);
                    // hide skeleton completely
                    HideBody();
                    return;
                }

                // character just died (was in another animation state)
                if (!character.IsNpc)
                {
                    // play death sound (please note - NPC death sound is played by network event only)
                    protoSkeleton.PlaySound(CharacterSound.Death, character);
                }

                clientState.SoundEmitterLoopCharacter.Stop();
                clientState.SoundEmitterLoopMovemement.Stop();

                // hide skeleton after timeout
                ClientTimersSystem.AddAction(
                    CorpseTimeoutSeconds,
                    HideBody);

                void HideBody()
                {
                    if (!publicState.IsDead)
                    {
                        // the character has been respawned
                        return;
                    }

                    skeletonRenderer.IsEnabled = false;
                    rendererShadow.IsEnabled   = false;
                }

                return;
            }

            skeletonRenderer.IsEnabled = true;
            rendererShadow.IsEnabled   = true;

            var appliedInput     = publicState.AppliedInput;
            var rotationAngleRad = GetCurrentRotationAngleRadInterpolated(character,
                                                                          clientState,
                                                                          appliedInput);

            var moveModes = appliedInput.MoveModes;

            if (publicState is PlayerCharacterPublicState playerCharacterPublicState &&
                playerCharacterPublicState.CurrentVehicle is { } vehicle)
            {
                var protoVehicle = (IProtoVehicle)vehicle.ProtoGameObject;
                protoVehicle.SharedGetSkeletonProto(vehicle, out var vehicleSkeleton, out _);
                if (vehicleSkeleton is null)
                {
                    // this vehicle doesn't provide any skeleton so player is simply displayed as standing idle inside it
                    moveModes = CharacterMoveModes.None;
                }
            }

            protoSkeleton.GetCurrentAnimationSetting(
                character,
                moveModes,
                rotationAngleRad,
                clientState.LastViewOrientation,
                out var newAnimationStarterName,
                out var newAnimationName,
                out var currentDrawMode,
                out var aimCoef,
                out var viewOrientation,
                out var isIdle);

            clientState.LastViewOrientation = viewOrientation;

            // TODO: consider adding new field - HasBackwardAnimations
            if (!protoSkeleton.HasMoveStartAnimations)
            {
                newAnimationStarterName = null;
                if (newAnimationName == "RunSideBackward")
                {
                    newAnimationName = "RunSide";
                }
            }

            var currentSkeleton = viewOrientation.IsUp && protoSkeleton.SkeletonResourceBack is not null
                                          ? protoSkeleton.SkeletonResourceBack
                                          : protoSkeleton.SkeletonResourceFront;

            if (skeletonRenderer.CurrentSkeleton != currentSkeleton)
            {
                // switch skeleton completely
                skeletonRenderer.SelectCurrentSkeleton(
                    currentSkeleton,
                    // please note: no starter animation in that case!
                    animationName: newAnimationName,
                    isLooped: true);
            }
            else
            {
                var activeAnimationName = skeletonRenderer.GetLatestAddedAnimationName(
                    trackIndex: AnimationTrackIndexes.Primary);

                if (newAnimationName != activeAnimationName &&
                    newAnimationStarterName != activeAnimationName)
                {
                    //Api.Logger.WriteDev(
                    //	newAnimationStarterName is not null
                    //	? $"Changing move animation: {activeAnimationName}->{newAnimationStarterName}->{newAnimationName}"
                    //	: $"Changing move animation: {activeAnimationName}->{newAnimationName}");

                    skeletonRenderer.RemoveAnimationTrackNextEntries(
                        trackIndex: AnimationTrackIndexes.Primary);

                    var currentAnimationName = skeletonRenderer.GetCurrentAnimationName(
                        trackIndex: AnimationTrackIndexes.Primary,
                        getLatestAddedAnimation: false);

                    var hasStarterAnimation = newAnimationStarterName is not null;
                    if (newAnimationName != "Idle" &&
                        hasStarterAnimation)
                    {
                        // add starter animation
                        skeletonRenderer.SetAnimationLoopMode(AnimationTrackIndexes.Primary,
                                                              isLooped: false);

                        if (newAnimationStarterName != currentAnimationName)
                        {
                            if (currentAnimationName == "Idle")
                            {
                                //Api.Logger.Dev("Adding starter animation: "
                                //               + newAnimationStarterName
                                //               + " (current animation is "
                                //               + currentAnimationName
                                //               + ")");

                                skeletonRenderer.SetAnimation(
                                    trackIndex: AnimationTrackIndexes.Primary,
                                    animationName: newAnimationStarterName,
                                    isLooped: false);
                            }
                            else
                            {
                                //Api.Logger.Dev("No starter animation will be added. Current animation: "
                                //               + currentAnimationName
                                //               + " starter name: "
                                //               + newAnimationStarterName);
                                //skeletonRenderer.AddAnimation(
                                //    trackIndex: AnimationTrackIndexes.Primary,
                                //    animationName: newAnimationStarterName,
                                //    isLooped: false);
                            }
                        }
                        else
                        {
                            //Api.Logger.Dev("No need to add starter animation: "
                            //               + newAnimationStarterName
                            //               + " (current animation is "
                            //               + currentAnimationName
                            //               + ")");
                        }

                        // add looped animation
                        skeletonRenderer.AddAnimation(
                            trackIndex: AnimationTrackIndexes.Primary,
                            animationName: newAnimationName,
                            isLooped: true);
                    }
                    else
                    {
                        if (currentAnimationName is not null &&
                            currentAnimationName.EndsWith("Start"))
                        {
                            //Api.Logger.Dev("Adding new animation + Abort animation after START animation: "
                            //               + newAnimationName
                            //               + " (current animation is "
                            //               + currentAnimationName
                            //               + ")");

                            // going into idle when playing a start animation - allow to finish it!

                            // remove queued entries
                            skeletonRenderer.RemoveAnimationTrackNextEntries(
                                trackIndex: AnimationTrackIndexes.Primary);

                            // add abort animation
                            skeletonRenderer.AddAnimation(
                                trackIndex: AnimationTrackIndexes.Primary,
                                animationName: currentAnimationName + "Abort",
                                isLooped: false);

                            // add looped animation
                            skeletonRenderer.AddAnimation(
                                trackIndex: AnimationTrackIndexes.Primary,
                                animationName: newAnimationName,
                                isLooped: true);
                        }
                        else if (currentAnimationName is not null &&
                                 currentAnimationName.EndsWith("Abort"))
                        {
                            //Api.Logger.Dev("Adding new animation after Abort animation: "
                            //               + newAnimationName
                            //               + " (current animation is "
                            //               + currentAnimationName
                            //               + ")");

                            // remove queued entries
                            skeletonRenderer.RemoveAnimationTrackNextEntries(
                                trackIndex: AnimationTrackIndexes.Primary);

                            // add end animation
                            skeletonRenderer.AddAnimation(
                                trackIndex: AnimationTrackIndexes.Primary,
                                animationName: currentAnimationName + "Abort",
                                isLooped: false);

                            // add looped animation
                            skeletonRenderer.AddAnimation(
                                trackIndex: AnimationTrackIndexes.Primary,
                                animationName: newAnimationName,
                                isLooped: true);
                        }
        public static void RefreshCharacterFinalStatsCache(
            IReadOnlyStatsDictionary protoEffects,
            ICharacterPublicState publicState,
            BaseCharacterPrivateState privateState,
            bool isFirstTime = false)
        {
            var containerEquipment = (publicState as ICharacterPublicStateWithEquipment)?.ContainerEquipment;

            privateState.ContainerEquipmentLastStateHash = containerEquipment?.StateHash;

            FinalStatsCache finalStatsCache;

            using (var tempStatsCache = TempStatsCache.GetFromPool())
            {
                // merge character prototype effects
                tempStatsCache.Merge(protoEffects);

                if (privateState is PlayerCharacterPrivateState playerCharacterPrivateState)
                {
                    // merge origin effects
                    if (playerCharacterPrivateState.Origin is not null)
                    {
                        tempStatsCache.Merge(playerCharacterPrivateState.Origin.Effects);
                    }

                    // merge skill effects
                    var skills = playerCharacterPrivateState.Skills;
                    skills.SharedFillEffectsCache(tempStatsCache);

                    // merge perks from tech nodes
                    foreach (var techNode in playerCharacterPrivateState.Technologies.Nodes)
                    {
                        foreach (var nodeEffect in techNode.NodeEffects)
                        {
                            if (nodeEffect is TechNodeEffectPerkUnlock perkUnlock)
                            {
                                tempStatsCache.Merge(perkUnlock.Perk.ProtoEffects);
                            }
                        }
                    }
                }

                foreach (var statusEffect in privateState.StatusEffects)
                {
                    if (statusEffect.IsDestroyed)
                    {
                        // the status effect might be already destroyed but still remain in the list
                        continue;
                    }

                    var protoStatusEffect = (IProtoStatusEffect)statusEffect.ProtoLogicObject;
                    tempStatsCache.Merge(protoStatusEffect.ProtoEffects);
                }

                if (containerEquipment is not null)
                {
                    // merge equipment effects
                    foreach (var item in containerEquipment.Items)
                    {
                        if (item.ProtoGameObject is IProtoItemEquipment protoEquipment &&
                            protoEquipment.SharedCanApplyEffects(item, containerEquipment))
                        {
                            tempStatsCache.Merge(protoEquipment.ProtoEffects);
                        }
                    }
                }

                // calculate the final stats cache
                finalStatsCache = tempStatsCache.CalculateFinalStatsCache();
            }

            privateState.FinalStatsCache = finalStatsCache;

            // need to recalculate the weapon cache as it depends on the final cache
            privateState.WeaponState.WeaponCache = null;

            ApplyFinalStatsCache(finalStatsCache, publicState.CurrentStats, isFirstTime);
        }
        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 ClientUpdateAnimation(
            ICharacter character,
            BaseCharacterClientState clientState,
            ICharacterPublicState publicState)
        {
            var skeletonRenderer = clientState.SkeletonRenderer;

            if (skeletonRenderer == null)
            {
                return;
            }

            var activeWeaponProto = publicState.CurrentItemWeaponProto;

            var protoSkeleton  = clientState.CurrentProtoSkeleton;
            var rendererShadow = clientState.RendererShadow;
            var wasDead        = clientState.IsDead;

            clientState.IsDead = publicState.IsDead;

            if (publicState.IsDead)
            {
                if (skeletonRenderer.GetCurrentAnimationName(AnimationTrackIndexes.Primary)
                    == "Death")
                {
                    // already in death animation
                    return;
                }

                // character was not dead on client and now become dead
                skeletonRenderer.ResetSkeleton();

                skeletonRenderer.SelectCurrentSkeleton(
                    protoSkeleton.SkeletonResourceFront,
                    "Idle",
                    isLooped: false);

                skeletonRenderer.SetAnimation(AnimationTrackIndexes.Primary, "Death", isLooped: false);

                if (!wasDead.HasValue)
                {
                    // character entered scope and it's dead
                    skeletonRenderer.SetAnimationTime(0, 10000);
                    // hide skeleton completely
                    HideBody();
                    return;
                }

                // character just died
                // play death sound
                protoSkeleton.PlaySound(CharacterSound.Death, character);
                clientState.SoundEmitterLoopCharacter.Stop();
                clientState.SoundEmitterLoopMovemement.Stop();

                // hide skeleton after timeout
                ClientTimersSystem.AddAction(
                    CorpseTimeoutSeconds,
                    HideBody);

                void HideBody()
                {
                    if (!publicState.IsDead)
                    {
                        // the character has been respawned
                        return;
                    }

                    skeletonRenderer.IsEnabled = false;
                    rendererShadow.IsEnabled   = false;
                }

                return;
            }

            skeletonRenderer.IsEnabled = true;
            rendererShadow.IsEnabled   = true;

            var appliedInput     = publicState.AppliedInput;
            var rotationAngleRad = GetCurrentRotationAngleRadInterpolated(character,
                                                                          clientState,
                                                                          appliedInput);

            GetCurrentAnimationSetting(
                protoSkeleton,
                appliedInput.MoveModes,
                rotationAngleRad,
                clientState.LastViewOrientation,
                out var newAnimationStarterName,
                out var newAnimationName,
                out var currentDrawMode,
                out var aimCoef,
                out var viewOrientation,
                out var isIdle);

            clientState.LastViewOrientation = viewOrientation;

            // TODO: consider adding new field - HasBackwardAnimations
            if (!protoSkeleton.HasMoveStartAnimations)
            {
                newAnimationStarterName = null;
                if (newAnimationName == "RunSideBackward")
                {
                    newAnimationName = "RunSide";
                }
            }

            var currentSkeleton = viewOrientation.IsUp && protoSkeleton.SkeletonResourceBack != null
                                      ? protoSkeleton.SkeletonResourceBack
                                      : protoSkeleton.SkeletonResourceFront;

            if (skeletonRenderer.CurrentSkeleton != currentSkeleton)
            {
                // switch skeleton completely
                skeletonRenderer.SelectCurrentSkeleton(
                    currentSkeleton,
                    // please note: no starter animation in that case!
                    animationName: newAnimationName,
                    isLooped: true);
            }
            else
            {
                var activeAnimationName =
                    skeletonRenderer.GetLatestAddedAnimationName(trackIndex: AnimationTrackIndexes.Primary);
                if (newAnimationName != activeAnimationName &&
                    newAnimationStarterName != activeAnimationName)
                {
                    //Api.Logger.WriteDev(
                    //	newAnimationStarterName != null
                    //	? $"Changing move animation: {activeAnimationName}->{newAnimationStarterName}->{newAnimationName}"
                    //	: $"Changing move animation: {activeAnimationName}->{newAnimationName}");

                    var hasStarterAnimation = newAnimationStarterName != null;
                    if (hasStarterAnimation)
                    {
                        // add starter animation
                        skeletonRenderer.SetAnimation(
                            trackIndex: AnimationTrackIndexes.Primary,
                            animationName: newAnimationStarterName,
                            isLooped: false);

                        // add looped animation
                        skeletonRenderer.AddAnimation(
                            trackIndex: AnimationTrackIndexes.Primary,
                            animationName: newAnimationName,
                            isLooped: true);
                    }
                    else if (newAnimationName == "Idle" &&
                             skeletonRenderer.GetCurrentAnimationName(trackIndex: AnimationTrackIndexes.Primary)
                             .EndsWith("Start"))
                    {
                        // going into idle when playing a start animation - allow to finish it!
                        // remove queued entries
                        skeletonRenderer.RemoveAnimationTrackNextEntries(trackIndex: AnimationTrackIndexes.Primary);

                        // add looped idle animation
                        skeletonRenderer.AddAnimation(
                            trackIndex: AnimationTrackIndexes.Primary,
                            animationName: newAnimationName,
                            isLooped: true);
                    }
                    else
                    {
                        // set looped animation
                        skeletonRenderer.SetAnimation(
                            trackIndex: AnimationTrackIndexes.Primary,
                            animationName: newAnimationName,
                            isLooped: true);
                    }
                }
            }

            if (appliedInput.MoveModes != CharacterMoveModes.None)
            {
                // moving mode
                var animationSpeedMultiplier = appliedInput.MoveSpeed
                                               / (protoSkeleton.DefaultMoveSpeed
                                                  * clientState.CurrentProtoSkeletonScaleMultiplier);

                skeletonRenderer.SetAnimationSpeed(
                    trackIndex: AnimationTrackIndexes.Primary,
                    speedMultiliper: (float)animationSpeedMultiplier);

                // moving - remove animation for static firing
                skeletonRenderer.RemoveAnimationTrack(AnimationTrackIndexes.ItemFiringStatic);
            }
            else
            {
                skeletonRenderer.SetAnimationSpeed(
                    trackIndex: AnimationTrackIndexes.Primary,
                    speedMultiliper: 1.0f);
            }

            skeletonRenderer.DrawMode = currentDrawMode;

            if (activeWeaponProto != null)
            {
                clientState.HasWeaponAnimationAssigned = true;
                clientState.LastAimCoef = aimCoef;

                var aimingAnimationName = activeWeaponProto.CharacterAnimationAimingName;
                if (aimingAnimationName != null)
                {
                    //Api.Logger.WriteDev(
                    //    $"Setting aiming animation: {aimingAnimationName} timePercents: {aimCoef:F2}");
                    skeletonRenderer.SetAnimationFrame(
                        trackIndex: AnimationTrackIndexes.ItemAiming,
                        animationName: aimingAnimationName,
                        timePositionPercents: aimCoef);
                }
                else
                {
                    skeletonRenderer.RemoveAnimationTrack(AnimationTrackIndexes.ItemAiming);
                }

                WeaponSystemClientDisplay.RefreshCurrentAttackAnimation(character);
            }
            else
            {
                clientState.HasWeaponAnimationAssigned = false;
            }

            SetLoopSounds(character, clientState, publicState.AppliedInput, protoSkeleton, isIdle);
        }
        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);
                }
            }
        }