private static void SetLoopSounds(
            ICharacter character,
            BaseCharacterClientState clientState,
            AppliedCharacterInput appliedInput,
            ProtoCharacterSkeleton protoSkeleton,
            bool isIdle)
        {
            CharacterSound soundKey;

            if (isIdle)
            {
                soundKey = CharacterSound.LoopIdle;
            }
            else
            {
                var isRunningMode = (appliedInput.MoveModes & CharacterMoveModes.ModifierRun) != 0;
                soundKey = isRunningMode ? CharacterSound.LoopRun : CharacterSound.LoopWalk;
            }

            var(soundPresetCharacter, soundPresetMovement)           = protoSkeleton.GetSoundPresets(character);
            clientState.SoundEmitterLoopCharacter.NextSoundResource  = soundPresetCharacter.GetSound(soundKey);
            clientState.SoundEmitterLoopMovemement.NextSoundResource = soundPresetMovement.GetSound(soundKey);

            // ensure the sounds are played
            clientState.SoundEmitterLoopCharacter.Play();
            clientState.SoundEmitterLoopMovemement.Play();
        }
Example #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;
        }
Example #3
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
            })
        private static double GetCurrentRotationAngleRadInterpolated(
            ICharacter character,
            BaseCharacterClientState clientState,
            AppliedCharacterInput appliedInput)
        {
            double rotationAngleRad = appliedInput.RotationAngleRad;

            if (!clientState.LastInterpolatedRotationAngleRad.HasValue)
            {
                // current character or first time - simply use current angle
                clientState.LastInterpolatedRotationAngleRad = rotationAngleRad;
                return(rotationAngleRad);
            }

            var oldAngle = clientState.LastInterpolatedRotationAngleRad.Value;

            if (Math.Abs(MathHelper.GetShortestAngleDist(oldAngle, rotationAngleRad))
                < Math.PI / 2)
            {
                // small difference - allow to interpolate
                // smooth interpolation for remote players is required to better deal with the network update rate
                // for local player it's much more fast and just improves overall smoothness of the character rotation
                var rate = character.IsCurrentClientCharacter
                               ? RotationAngleRadInterpolationRateCurrentCharacter
                               : RotationAngleRadInterpolationRateRemoteCharacter;

                rotationAngleRad = MathHelper.LerpAngle(
                    oldAngle,
                    rotationAngleRad,
                    Api.Client.Core.DeltaTime,
                    rate);
            }

            clientState.LastInterpolatedRotationAngleRad = rotationAngleRad;
            return(rotationAngleRad);
        }
        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 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);
        }