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(); }
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; }
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); }