< Summary

Class:AvatarAnimatorLegacy
Assembly:AvatarShape
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Avatar/AvatarAnimatorLegacy.cs
Covered lines:19
Uncovered lines:224
Coverable lines:243
Total lines:602
Line coverage:7.8% (19 of 243)
Covered branches:0
Total branches:0
Covered methods:8
Total methods:31
Method coverage:25.8% (8 of 31)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
AvatarAnimatorLegacy()0%110100%
Awake()0%2.062075%
Start()0%110100%
Prepare(...)0%12300%
PrepareLocomotionAnims(...)0%12300%
OnEventHandlerUpdate()0%2100%
OnPoolGet()0%220100%
OnPoolRelease()0%20400%
OnUpdateWithDeltaTime(...)0%6200%
UpdateInterface()0%1101000%
State_Init(...)0%6200%
State_Ground(...)0%56700%
CrossFadeTo(...)0%30500%
State_Air(...)0%12300%
State_Expression(...)0%1101000%
GetCurrentEmoteLoopCount()0%12300%
StopEmote(...)0%2100%
StopEmoteInternal(...)0%90900%
StartEmote(...)0%30500%
Reset()0%6200%
SetIdleFrame()0%2100%
PlayEmote(...)0%1321100%
EquipBaseClip(...)0%12300%
EquipEmote(...)0%20400%
UnequipEmote(...)0%30500%
GetCurrentEmoteId()0%110100%
InitializeAvatarAudioAndParticleHandlers(...)0%12300%
OnEnable()0%2.862040%
OnDisable()0%3.462028.57%
AsyncAnimationStop()0%20400%
OnDestroy()0%2.152066.67%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Avatar/AvatarAnimatorLegacy.cs

#LineLine coverage
 1using AvatarSystem;
 2using Cysharp.Threading.Tasks;
 3using DCL;
 4using DCL.Components;
 5using DCL.Emotes;
 6using DCL.Helpers;
 7using System;
 8using System.Collections.Generic;
 9using UnityEngine;
 10using Environment = DCL.Environment;
 11
 12public enum AvatarAnimation
 13{
 14    IDLE,
 15    RUN,
 16    WALK,
 17    EMOTE,
 18    JUMP,
 19    FALL,
 20}
 21
 22public static class AvatarAnimationExtensions
 23{
 24    public static bool ShouldLoop(this AvatarAnimation avatarAnimation)
 25    {
 26        return avatarAnimation == AvatarAnimation.RUN ||
 27               avatarAnimation == AvatarAnimation.IDLE ||
 28               avatarAnimation == AvatarAnimation.FALL ||
 29               avatarAnimation == AvatarAnimation.WALK;
 30    }
 31}
 32
 33public class AvatarAnimatorLegacy : MonoBehaviour, IPoolLifecycleHandler, IAnimator
 34{
 35    private const float ZERO_POSE_LOOP_TIME_SECONDS = 2f;
 36    private const float IDLE_TRANSITION_TIME = 0.2f;
 37    private const float RUN_TRANSITION_TIME = 0.15f;
 38    private const float WALK_TRANSITION_TIME = 0.15f;
 39    private const float WALK_MAX_SPEED = 6;
 40    private const float RUN_MIN_SPEED = 4f;
 41    private const float WALK_MIN_SPEED = 0.1f;
 42    private const float WALK_RUN_SWITCH_TIME = 1.5f;
 43    private const float JUMP_TRANSITION_TIME = 0.01f;
 44    private const float FALL_TRANSITION_TIME = 0.5f;
 45    private const float EXPRESSION_EXIT_TRANSITION_TIME = 0.2f;
 46    private const float EXPRESSION_ENTER_TRANSITION_TIME = 0.1f;
 47    private const float OTHER_PLAYER_MOVE_THRESHOLD = 0.5f;
 48
 49    private const float AIR_EXIT_TRANSITION_TIME = 0.2f;
 50
 51    private const float ELEVATION_OFFSET = 0.6f;
 52    private const float RAY_OFFSET_LENGTH = 3.0f;
 53
 54    // Time it takes to determine if a character is grounded when vertical velocity is 0
 55    private const float FORCE_GROUND_TIME = 0.05f;
 56
 57    // Minimum vertical speed used to consider whether an avatar is on air
 58    private const float MIN_VERTICAL_SPEED_AIR = 0.025f;
 59
 60    [Serializable]
 61    public class AvatarLocomotion
 62    {
 63        public AnimationClip idle;
 64        public AnimationClip walk;
 65        public AnimationClip run;
 66        public AnimationClip jump;
 67        public AnimationClip fall;
 68    }
 69
 70    [Serializable]
 71    public class BlackBoard
 72    {
 73        public float walkSpeedFactor;
 74        public float runSpeedFactor;
 75        public float movementSpeed;
 76        public float verticalSpeed;
 77        public bool isGrounded;
 78        public string expressionTriggerId;
 79        public long expressionTriggerTimestamp;
 80        public float deltaTime;
 81        public bool shouldLoop;
 82    }
 83
 84    [SerializeField] internal AvatarLocomotion femaleLocomotions;
 85    [SerializeField] internal AvatarLocomotion maleLocomotions;
 65786    [SerializeField] internal string renderingLayer = "Default";
 87    private AvatarLocomotion currentLocomotions;
 88
 89    public new Animation animation;
 90    public BlackBoard blackboard;
 91    public Transform target;
 92
 93    private Action<BlackBoard> currentState;
 94
 95    private Vector3 lastPosition;
 96    private bool isOwnPlayer;
 97    private AvatarAnimationEventHandler animEventHandler;
 98
 99    private float lastOnAirTime = 0;
 100
 657101    private Dictionary<string, EmoteClipData> emoteClipDataMap = new ();
 102
 103    private string runAnimationName;
 104    private string walkAnimationName;
 105    private string idleAnimationName;
 106    private string jumpAnimationName;
 107    private string fallAnimationName;
 108    private AvatarAnimation latestAnimationState;
 109    private AnimationState runAnimationState;
 110    private AnimationState walkAnimationState;
 111    private bool isUpdateRegistered = false;
 112
 113    private Ray rayCache;
 114    private bool hasTarget;
 115    private EmoteClipData lastExtendedEmoteData;
 116    private string lastCrossFade;
 117    private AnimationState currentEmote;
 118    private int lastEmoteLoopCount;
 119
 120    private float lastZeroPoseTime;
 121    private int zeroPoseLoop;
 122
 123    private void Awake()
 124    {
 634125        hasTarget = target != null;
 126
 634127        if (!hasTarget)
 0128            Debug.LogError(message: $"Target is not assigned. {nameof(UpdateInterface)} will not work correctly.", this)
 634129    }
 130
 131    public void Start()
 132    {
 302133        OnPoolGet();
 302134    }
 135
 136    // AvatarSystem entry points
 137    public bool Prepare(string bodyshapeId, GameObject container)
 138    {
 0139        StopEmote(true);
 140
 0141        animation = container.gameObject.GetOrCreateComponent<Animation>();
 0142        container.gameObject.GetOrCreateComponent<StickerAnimationListener>();
 143
 0144        PrepareLocomotionAnims(bodyshapeId);
 0145        SetIdleFrame();
 0146        animation.Sample();
 0147        InitializeAvatarAudioAndParticleHandlers(animation);
 148
 149        // since the avatar can be updated when changing a wearable we shouldn't register to the update event twice
 0150        if (!isUpdateRegistered)
 151        {
 0152            isUpdateRegistered = true;
 153
 0154            if (isOwnPlayer) { DCLCharacterController.i.OnUpdateFinish += OnUpdateWithDeltaTime; }
 0155            else { Environment.i.platform.updateEventHandler.AddListener(IUpdateEventHandler.EventType.Update, OnEventHa
 156        }
 157
 0158        return true;
 159    }
 160
 161    private void PrepareLocomotionAnims(string bodyshapeId)
 162    {
 0163        if (bodyshapeId.Contains(WearableLiterals.BodyShapes.MALE)) { currentLocomotions = maleLocomotions; }
 0164        else if (bodyshapeId.Contains(WearableLiterals.BodyShapes.FEMALE)) { currentLocomotions = femaleLocomotions; }
 165
 0166        EquipBaseClip(currentLocomotions.idle);
 0167        EquipBaseClip(currentLocomotions.walk);
 0168        EquipBaseClip(currentLocomotions.run);
 0169        EquipBaseClip(currentLocomotions.jump);
 0170        EquipBaseClip(currentLocomotions.fall);
 171
 0172        idleAnimationName = currentLocomotions.idle.name;
 0173        walkAnimationName = currentLocomotions.walk.name;
 0174        runAnimationName = currentLocomotions.run.name;
 0175        jumpAnimationName = currentLocomotions.jump.name;
 0176        fallAnimationName = currentLocomotions.fall.name;
 177
 0178        runAnimationState = animation[runAnimationName];
 0179        walkAnimationState = animation[walkAnimationName];
 0180    }
 181
 182    private void OnEventHandlerUpdate()
 183    {
 0184        OnUpdateWithDeltaTime(Time.deltaTime);
 0185    }
 186
 187    public void OnPoolGet()
 188    {
 302189        if (DCLCharacterController.i != null)
 190        {
 302191            isOwnPlayer = DCLCharacterController.i.transform == transform.parent;
 192
 193            // NOTE: disable MonoBehaviour's update to use DCLCharacterController event instead
 302194            this.enabled = !isOwnPlayer;
 195        }
 196
 302197        currentState = State_Init;
 302198    }
 199
 200    public void OnPoolRelease()
 201    {
 0202        if (isUpdateRegistered)
 203        {
 0204            isUpdateRegistered = false;
 205
 0206            if (isOwnPlayer && DCLCharacterController.i) { DCLCharacterController.i.OnUpdateFinish -= OnUpdateWithDeltaT
 0207            else { Environment.i.platform.updateEventHandler.RemoveListener(IUpdateEventHandler.EventType.Update, OnEven
 208        }
 0209    }
 210
 211    private void OnUpdateWithDeltaTime(float deltaTime)
 212    {
 0213        blackboard.deltaTime = deltaTime;
 0214        UpdateInterface();
 0215        currentState?.Invoke(blackboard);
 0216    }
 217
 218    private void UpdateInterface()
 219    {
 0220        if (!target) return;
 221
 0222        Vector3 velocityTargetPosition = target.position;
 0223        Vector3 flattenedVelocity = velocityTargetPosition - lastPosition;
 224
 225        //NOTE(Brian): Vertical speed
 0226        float verticalVelocity = flattenedVelocity.y;
 227
 228        //NOTE(Kinerius): if we have more or less than zero we consider that we are either jumping or falling
 0229        if (Mathf.Abs(verticalVelocity) > MIN_VERTICAL_SPEED_AIR) { lastOnAirTime = Time.time; }
 230
 0231        blackboard.verticalSpeed = verticalVelocity;
 232
 0233        flattenedVelocity.y = 0;
 234
 0235        if (isOwnPlayer)
 0236            blackboard.movementSpeed = flattenedVelocity.magnitude - DCLCharacterController.i.movingPlatformSpeed;
 237        else
 0238            blackboard.movementSpeed = flattenedVelocity.magnitude;
 239
 0240        Vector3 rayOffset = Vector3.up * RAY_OFFSET_LENGTH;
 241
 242        //NOTE(Kinerius): This check is just for the playing character, it uses a combination of collision flags and ray
 0243        bool isGroundedByCharacterController = isOwnPlayer && DCLCharacterController.i.isGrounded;
 244
 245        //NOTE(Kinerius): This check is for interpolated avatars (the other players) as we dont have a Character Control
 246        //                this check is cheap and fast but not precise
 0247        bool isGroundedByVelocity = !isOwnPlayer && Time.time - lastOnAirTime > FORCE_GROUND_TIME;
 248
 249        //NOTE(Kinerius): This additional check is both for the player and interpolated avatars, we cast an additional r
 0250        bool isGroundedByRaycast = false;
 251
 0252        if (!isGroundedByCharacterController && !isGroundedByVelocity)
 253        {
 0254            rayCache.origin = velocityTargetPosition + rayOffset;
 0255            rayCache.direction = Vector3.down;
 256
 0257            LayerMask iGroundLayers = DCLCharacterController.i?.groundLayers ?? new LayerMask();
 258
 0259            isGroundedByRaycast = Physics.Raycast(rayCache,
 260                RAY_OFFSET_LENGTH - ELEVATION_OFFSET,
 261                iGroundLayers);
 262        }
 263
 0264        blackboard.isGrounded = isGroundedByCharacterController || isGroundedByVelocity || isGroundedByRaycast;
 265
 0266        lastPosition = velocityTargetPosition;
 0267    }
 268
 269    private void State_Init(BlackBoard bb)
 270    {
 0271        if (bb.isGrounded) { currentState = State_Ground; }
 0272        else { currentState = State_Air; }
 0273    }
 274
 275    private void State_Ground(BlackBoard bb)
 276    {
 0277        if (bb.deltaTime <= 0)
 0278            return;
 279
 0280        float movementSpeed = bb.movementSpeed / bb.deltaTime;
 281
 0282        runAnimationState.normalizedSpeed = movementSpeed * bb.runSpeedFactor;
 0283        walkAnimationState.normalizedSpeed = movementSpeed * bb.walkSpeedFactor;
 284
 0285        if (movementSpeed >= WALK_MAX_SPEED)
 0286            CrossFadeTo(AvatarAnimation.RUN, runAnimationName, RUN_TRANSITION_TIME);
 0287        else if (movementSpeed >= RUN_MIN_SPEED && movementSpeed < WALK_MAX_SPEED)
 0288            CrossFadeTo(AvatarAnimation.WALK, walkAnimationName, WALK_TRANSITION_TIME);
 0289        else if (movementSpeed > WALK_MIN_SPEED)
 0290            CrossFadeTo(AvatarAnimation.WALK, walkAnimationName, WALK_TRANSITION_TIME);
 291        else
 0292            CrossFadeTo(AvatarAnimation.IDLE, idleAnimationName, IDLE_TRANSITION_TIME);
 293
 0294        if (!bb.isGrounded)
 295        {
 0296            currentState = State_Air;
 0297            OnUpdateWithDeltaTime(bb.deltaTime);
 298        }
 0299    }
 300
 301    private void CrossFadeTo(AvatarAnimation animationState, string animationName,
 302        float runTransitionTime, PlayMode playMode = PlayMode.StopSameLayer)
 303    {
 0304        if (latestAnimationState == animationState)
 0305            return;
 306
 0307        lastCrossFade = animationName;
 0308        latestAnimationState = animationState;
 0309        animation.wrapMode = latestAnimationState.ShouldLoop() || blackboard.shouldLoop ? WrapMode.Loop : WrapMode.Once;
 0310        animation.CrossFade(lastCrossFade, runTransitionTime, playMode);
 0311    }
 312
 313    private void State_Air(BlackBoard bb)
 314    {
 0315        if (bb.verticalSpeed > 0) { CrossFadeTo(AvatarAnimation.JUMP, jumpAnimationName, JUMP_TRANSITION_TIME, PlayMode.
 0316        else { CrossFadeTo(AvatarAnimation.FALL, fallAnimationName, FALL_TRANSITION_TIME, PlayMode.StopAll); }
 317
 0318        if (bb.isGrounded)
 319        {
 0320            animation.Blend(jumpAnimationName, 0, AIR_EXIT_TRANSITION_TIME);
 0321            animation.Blend(fallAnimationName, 0, AIR_EXIT_TRANSITION_TIME);
 0322            currentState = State_Ground;
 0323            OnUpdateWithDeltaTime(bb.deltaTime);
 324        }
 0325    }
 326
 327    private void State_Expression(BlackBoard bb)
 328    {
 0329        var prevAnimation = latestAnimationState;
 330
 0331        var exitTransitionStarted = false;
 332
 0333        if (!bb.isGrounded)
 334        {
 0335            currentState = State_Air;
 0336            exitTransitionStarted = true;
 337        }
 338
 0339        if (ExpressionGroundTransitionCondition(animationState: animation[bb.expressionTriggerId]))
 340        {
 0341            currentState = State_Ground;
 0342            exitTransitionStarted = true;
 343        }
 344
 0345        if (exitTransitionStarted)
 0346            StopEmoteInternal(false);
 0347        else if (prevAnimation != AvatarAnimation.EMOTE) // this condition makes Blend be called only in first frame of 
 348        {
 0349            animation.wrapMode = bb.shouldLoop ? WrapMode.Loop : WrapMode.Once;
 0350            animation.Blend(bb.expressionTriggerId, 1, EXPRESSION_ENTER_TRANSITION_TIME);
 351        }
 352
 353        // If we reach the emote loop, we send the RPC message again to refresh new users
 0354        if (bb.shouldLoop && isOwnPlayer)
 355        {
 0356            int emoteLoop = GetCurrentEmoteLoopCount();
 357
 0358            if (emoteLoop != lastEmoteLoopCount) { UserProfile.GetOwnUserProfile().SetAvatarExpression(bb.expressionTrig
 359
 0360            lastEmoteLoopCount = emoteLoop;
 361        }
 362
 0363        return;
 364
 365        bool ExpressionGroundTransitionCondition(AnimationState animationState)
 366        {
 0367            float timeTillEnd = animationState == null ? 0 : animationState.length - animationState.time;
 0368            bool isAnimationOver = timeTillEnd < EXPRESSION_EXIT_TRANSITION_TIME && !bb.shouldLoop;
 0369            bool isMoving = isOwnPlayer && DCLCharacterController.i.isMovingByUserInput; //Math.Abs(bb.movementSpeed) > 
 370
 0371            bool expressionGroundTransitionCondition = isAnimationOver || isMoving;
 0372            return expressionGroundTransitionCondition;
 373        }
 374    }
 375
 376    private int GetCurrentEmoteLoopCount()
 377    {
 378        // Some scenes might use animations with 0 length as loop poses (not ideal)
 0379        if (Mathf.Approximately(currentEmote.length, 0))
 380        {
 0381            if (Time.time - lastZeroPoseTime > ZERO_POSE_LOOP_TIME_SECONDS)
 382            {
 0383                zeroPoseLoop++;
 0384                lastZeroPoseTime = Time.time;
 0385                return zeroPoseLoop;
 386            }
 387        }
 388
 0389        return Mathf.RoundToInt(currentEmote.time / currentEmote.length);
 390    }
 391
 392    public void StopEmote(bool immediate)
 393    {
 0394        StopEmoteInternal(immediate);
 0395    }
 396
 397    private void StopEmoteInternal(bool immediate)
 398    {
 0399        if (animation == null) return;
 400
 0401        if (!string.IsNullOrEmpty(blackboard.expressionTriggerId))
 0402            animation.Blend(blackboard.expressionTriggerId, 0, !immediate ? EXPRESSION_EXIT_TRANSITION_TIME : 0);
 403
 404        // Instantly replicate our emote status and position
 0405        if (isOwnPlayer && !string.IsNullOrEmpty(blackboard.expressionTriggerId))
 406        {
 0407            DCLCharacterController.i.ReportMovement();
 0408            UserProfile.GetOwnUserProfile().SetAvatarExpression("", UserProfile.EmoteSource.EmoteCancel, true);
 409        }
 410
 0411        blackboard.expressionTriggerId = null;
 0412        blackboard.shouldLoop = false;
 0413        lastExtendedEmoteData?.Stop();
 414
 0415        if (!immediate) OnUpdateWithDeltaTime(blackboard.deltaTime);
 0416    }
 417
 418    private void StartEmote(string emoteId, bool spatial, bool occlude)
 419    {
 0420        if (!string.IsNullOrEmpty(emoteId))
 421        {
 0422            lastExtendedEmoteData?.Stop();
 423
 0424            if (emoteClipDataMap.TryGetValue(emoteId, out var emoteClipData))
 425            {
 0426                lastExtendedEmoteData = emoteClipData;
 427
 0428                emoteClipData.Play(gameObject.layer, spatial, occlude);
 429            }
 430        }
 0431        else { lastExtendedEmoteData?.Stop(); }
 0432    }
 433
 434    public void Reset()
 435    {
 0436        if (animation == null)
 0437            return;
 438
 439        //It will set the animation to the first frame, but due to the nature of the script and its Update. It wont stop
 0440        animation.Stop();
 0441    }
 442
 443    public void SetIdleFrame()
 444    {
 0445        animation.Play(currentLocomotions.idle.name);
 0446    }
 447
 448    public void PlayEmote(string emoteId, long timestamps, bool spatial, bool occlude,
 449        bool forcePlay)
 450    {
 0451        if (animation == null)
 0452            return;
 453
 0454        if (string.IsNullOrEmpty(emoteId))
 0455            return;
 456
 0457        if (animation.GetClip(emoteId) == null)
 0458            return;
 459
 0460        bool loop = emoteClipDataMap.TryGetValue(emoteId, out var clipData) && clipData.Loop;
 0461        bool mustTriggerAnimation = !string.IsNullOrEmpty(emoteId) && (blackboard.expressionTriggerTimestamp != timestam
 462
 0463        bool isTheSameLoopingEmote = loop && blackboard.expressionTriggerId == emoteId;
 464
 465        // Triggering an emote manually updates the timestamp, the looping emote by itself sends a timestamp of -1
 466        // so if we are already using an emote that looped and we receive the play emote command with that timestamp, we
 0467        if (isTheSameLoopingEmote && timestamps < 0)
 0468            return;
 469
 0470        blackboard.expressionTriggerId = emoteId;
 0471        blackboard.expressionTriggerTimestamp = timestamps;
 472
 0473        if (mustTriggerAnimation || loop)
 474        {
 0475            StartEmote(emoteId, spatial, occlude);
 476
 0477            if (!string.IsNullOrEmpty(emoteId))
 478            {
 0479                animation.Stop(emoteId);
 0480                latestAnimationState = AvatarAnimation.IDLE;
 481            }
 482
 0483            blackboard.shouldLoop = loop;
 484
 0485            CrossFadeTo(AvatarAnimation.EMOTE, emoteId, EXPRESSION_EXIT_TRANSITION_TIME, PlayMode.StopAll);
 486
 0487            currentEmote = animation[emoteId];
 0488            lastEmoteLoopCount = GetCurrentEmoteLoopCount();
 0489            currentState = State_Expression;
 490        }
 0491    }
 492
 493    public void EquipBaseClip(AnimationClip clip)
 494    {
 0495        var clipId = clip.name;
 496
 0497        if (animation == null)
 0498            return;
 499
 0500        if (animation.GetClip(clipId) != null)
 0501            animation.RemoveClip(clipId);
 502
 0503        animation.AddClip(clip, clipId);
 0504    }
 505
 506    public void EquipEmote(string emoteId, EmoteClipData emoteClipData)
 507    {
 0508        if (animation == null)
 0509            return;
 510
 0511        if (animation.GetClip(emoteId) != null)
 0512            animation.RemoveClip(emoteId);
 513
 0514        emoteClipDataMap[emoteId] = emoteClipData;
 515
 0516        animation.AddClip(emoteClipData.AvatarClip, emoteId);
 517
 0518        if (emoteClipData.ExtraContent != null)
 519        {
 0520            emoteClipData.ExtraContent.transform.SetParent(animation.transform.parent, false);
 0521            emoteClipData.ExtraContent.transform.ResetLocalTRS();
 0522            emoteClipData.ExtraContent.transform.localPosition = animation.transform.localPosition;
 523        }
 0524    }
 525
 526    public void UnequipEmote(string emoteId)
 527    {
 0528        if (animation == null)
 0529            return;
 530
 0531        if (animation.GetClip(emoteId) == null)
 0532            return;
 533
 0534        animation.RemoveClip(emoteId);
 535
 0536        if (emoteClipDataMap.TryGetValue(emoteId, out var emoteClipData))
 537        {
 0538            if (emoteClipData.ExtraContent != null)
 0539                emoteClipData.ExtraContent.transform.SetParent(null, false);
 540        }
 0541    }
 542
 543    public string GetCurrentEmoteId() =>
 1544        blackboard.expressionTriggerId;
 545
 546    private void InitializeAvatarAudioAndParticleHandlers(Animation createdAnimation)
 547    {
 548        //NOTE(Mordi): Adds handler for animation events, and passes in the audioContainer for the avatar
 0549        AvatarAnimationEventHandler animationEventHandler = createdAnimation.gameObject.GetOrCreateComponent<AvatarAnima
 0550        AudioContainer audioContainer = transform.GetComponentInChildren<AudioContainer>();
 551
 0552        if (audioContainer != null)
 553        {
 0554            animationEventHandler.Init(audioContainer, LayerMask.NameToLayer(renderingLayer));
 555
 556            //NOTE(Mordi): If this is a remote avatar, pass the animation component so we can keep track of whether it i
 0557            AvatarAudioHandlerRemote audioHandlerRemote = audioContainer.GetComponent<AvatarAudioHandlerRemote>();
 558
 0559            if (audioHandlerRemote != null) { audioHandlerRemote.Init(createdAnimation.gameObject); }
 560        }
 561
 0562        animEventHandler = animationEventHandler;
 0563    }
 564
 565    private void OnEnable()
 566    {
 613567        if (animation == null)
 613568            return;
 569
 0570        animation.enabled = true;
 0571        SetIdleFrame();
 0572    }
 573
 574    private void OnDisable()
 575    {
 613576        if (animation == null)
 613577            return;
 578
 0579        StopEmoteInternal(true);
 0580        CrossFadeTo(AvatarAnimation.IDLE, idleAnimationName, 0);
 0581        currentState = State_Ground;
 582
 0583        AsyncAnimationStop().Forget();
 0584    }
 585
 586    private async UniTaskVoid AsyncAnimationStop()
 587    {
 0588        await UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate);
 589
 0590        if (animation == null)
 0591            return;
 592
 0593        animation.Stop();
 0594        animation.enabled = false;
 0595    }
 596
 597    private void OnDestroy()
 598    {
 634599        if (animEventHandler != null)
 0600            Destroy(animEventHandler);
 634601    }
 602}