< Summary

Class:DCL.ECSComponents.AvatarShape
Assembly:DCL.ECSComponents.AvatarShape
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/DCLPlugins/ECS7/ECSComponents/AvatarShape/AvatarShape.cs
Covered lines:42
Uncovered lines:158
Coverable lines:200
Total lines:474
Line coverage:21% (42 of 200)
Covered branches:0
Total branches:0
Covered methods:6
Total methods:27
Method coverage:22.2% (6 of 27)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
AvatarShape()0%110100%
Awake()0%330100%
Init()0%2100%
SetPlayerNameReference()0%12300%
PlayerClicked()0%6200%
OnDestroy()0%3.143075%
ApplyModel()0%1561200%
LoadAvatar()0%90900%
SetImpostor(...)0%20400%
PlayerPointerExit()0%6200%
PlayerPointerEnter()0%6200%
UpdatePlayerStatus(...)0%3061700%
Update()0%12300%
DisablePassport()0%6200%
EnablePassport()0%6200%
OnEntityTransformChanged(...)0%2100%
OnEntityTransformChanged(...)0%12300%
OnPoolRelease()0%2100%
OnPoolGet()0%2100%
ApplyHideAvatarModifier()0%2100%
RemoveHideAvatarModifier()0%2100%
Cleanup()0%8.758077.27%
PrintCurrentProfile()0%2100%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/DCLPlugins/ECS7/ECSComponents/AvatarShape/AvatarShape.cs

#LineLine coverage
 1using AvatarSystem;
 2using Cysharp.Threading.Tasks;
 3using DCL.Components;
 4using DCL.Configuration;
 5using DCL.Controllers;
 6using DCL.Emotes;
 7using DCL.Helpers;
 8using DCL.Interface;
 9using DCL.Models;
 10using System;
 11using System.Collections.Generic;
 12using System.Linq;
 13using System.Threading;
 14using UnityEngine;
 15using LOD = AvatarSystem.LOD;
 16
 17namespace DCL.ECSComponents
 18{
 19    public interface IAvatarShape
 20    {
 21        /// <summary>
 22        /// This will initialize the AvatarShape and set
 23        /// </summary>
 24        void Init();
 25
 26        /// <summary>
 27        /// Clean up the avatar shape so we can reutilizate it using the pool
 28        /// </summary>
 29        void Cleanup();
 30
 31        /// <summary>
 32        /// Apply the model of the avatar, it will reload if necessary
 33        /// </summary>
 34        /// <param name="scene"></param>
 35        /// <param name="entity"></param>
 36        /// <param name="newModel"></param>
 37        void ApplyModel(IParcelScene scene, IDCLEntity entity, PBAvatarShape newModel);
 38
 39        /// <summary>
 40        /// Get the transform of the avatar shape
 41        /// </summary>
 42        Transform transform { get; }
 43
 44        /// <summary>
 45        /// Get non-monobehaviour internal IAvatar object that contains the merged renderer
 46        /// </summary>
 47        IAvatar internalAvatar { get; }
 48    }
 49
 50    public class AvatarShape : MonoBehaviour, IHideAvatarAreaHandler, IPoolableObjectContainer, IAvatarShape, IPoolLifec
 51    {
 52        private const float AVATAR_Y_AXIS_OFFSET = -0.72f;
 53        private const float MINIMUM_PLAYERNAME_HEIGHT = 2.7f;
 54        internal const string IN_HIDE_AREA = "IN_HIDE_AREA";
 55        private const string OPEN_PASSPORT_SOURCE = "World";
 56
 57        [SerializeField] private GameObject avatarContainer;
 58        [SerializeField] internal Collider avatarCollider;
 59
 60        [SerializeField] internal AvatarOnPointerDown onPointerDown;
 61        [SerializeField] internal AvatarOutlineOnHoverEvent outlineOnHover;
 62        [SerializeField] internal GameObject playerNameContainer;
 63        [SerializeField] private Transform baseAvatarContainer;
 64        [SerializeField] internal BaseAvatarReferences baseAvatarReferencesPrefab;
 65
 66        internal IAvatarMovementController avatarMovementController;
 67        internal IPlayerName playerName;
 68        internal IAvatarReporterController avatarReporterController;
 69
 70        private BaseVariable<(string playerId, string source)> currentPlayerInfoCardId;
 71
 72        internal bool initializedPosition = false;
 73
 74        internal Player player = null;
 075        internal BaseDictionary<string, Player> otherPlayers => DataStore.i.player.otherPlayers;
 76
 377        private IAvatarAnchorPoints anchorPoints = new AvatarAnchorPoints();
 78        internal IAvatar avatar;
 79        internal CancellationTokenSource loadingCts;
 80        private ILazyTextureObserver currentLazyObserver;
 81        private IUserProfileBridge userProfileBridge;
 382        private bool isGlobalSceneAvatar = true;
 83
 484        public IPoolableObject poolableObject { get; set; }
 85        internal PBAvatarShape model;
 86        internal IDCLEntity entity;
 87
 88        private Service<IAvatarFactory> avatarFactory;
 89        private Service<IEmotesCatalogService> emotesCatalog;
 90        private IAvatarEmotesController emotesController;
 91        private AvatarSceneEmoteHandler sceneEmoteHandler;
 92        private IBaseAvatarReferences baseAvatarReferences;
 393        private readonly OnPointerEvent.Model viewProfilePointerModel = new ()
 94        {
 95            type = OnPointerDown.NAME,
 96            button = WebInterface.ACTION_BUTTON.POINTER.ToString(),
 97            hoverText = "View Profile",
 98        };
 099        public IAvatar internalAvatar => avatar;
 100
 101        private void Awake()
 102        {
 2103            avatarMovementController = GetComponent<IAvatarMovementController>();
 104            // TODO: avoid instantiation, user profile bridge should be retrieved from the service locator
 2105            userProfileBridge = new UserProfileWebInterfaceBridge();
 106
 2107            currentPlayerInfoCardId = DataStore.i.HUDs.currentPlayerId;
 2108            Visibility visibility = new Visibility();
 2109            avatarMovementController.SetAvatarTransform(transform);
 110
 111            // Right now the avatars that are not part of the global scene of avatar are not using LOD since
 112            // AvatarsLodController are no taking them into account. It needs product definition and a refactor to inclu
 2113            LOD avatarLOD = new LOD(avatarContainer, visibility, avatarMovementController);
 2114            AvatarAnimatorLegacy animator = GetComponentInChildren<AvatarAnimatorLegacy>();
 115
 116
 117            //Ensure base avatar references
 2118            baseAvatarReferences = baseAvatarContainer.GetComponentInChildren<IBaseAvatarReferences>() ?? Instantiate(ba
 119
 2120            avatar = avatarFactory.Ref.CreateAvatarWithHologram(avatarContainer, new BaseAvatar(baseAvatarReferences), a
 121
 2122            emotesController = avatar.GetEmotesController();
 123
 2124            sceneEmoteHandler = new AvatarSceneEmoteHandler(
 125                emotesController,
 126                Environment.i.serviceLocator.Get<IEmotesService>());
 127
 2128            avatarReporterController ??= new AvatarReporterController(Environment.i.world.state);
 129
 2130            onPointerDown.OnPointerDownReport += PlayerClicked;
 2131            onPointerDown.OnPointerEnterReport += PlayerPointerEnter;
 2132            onPointerDown.OnPointerExitReport += PlayerPointerExit;
 2133            outlineOnHover.OnPointerEnterReport += PlayerPointerEnter;
 2134            outlineOnHover.OnPointerExitReport += PlayerPointerExit;
 2135        }
 136
 137        public void Init()
 138        {
 139            // The avatars have an offset in the Y axis, so we set the offset after the avatar has been restored from th
 0140            transform.position = new Vector3(transform.position.x, AVATAR_Y_AXIS_OFFSET, transform.position.z);
 0141            SetPlayerNameReference();
 0142        }
 143
 144        private void SetPlayerNameReference()
 145        {
 0146            if (playerName != null)
 0147                return;
 0148            playerName = GetComponentInChildren<IPlayerName>();
 0149            playerName?.Hide(true);
 0150        }
 151
 152        private void PlayerClicked()
 153        {
 0154            if (model == null)
 0155                return;
 0156            currentPlayerInfoCardId.Set((model.Id, OPEN_PASSPORT_SOURCE));
 0157        }
 158
 159        public void OnDestroy()
 160        {
 2161            Cleanup();
 162
 2163            if (poolableObject != null && poolableObject.isInsidePool)
 0164                poolableObject.RemoveFromPool();
 2165        }
 166
 167        public async void ApplyModel(IParcelScene scene, IDCLEntity entity, PBAvatarShape newModel)
 168        {
 0169            this.entity = entity;
 170
 0171            isGlobalSceneAvatar = scene.sceneData.sceneNumber == EnvironmentSettings.AVATAR_GLOBAL_SCENE_NUMBER;
 172
 0173            DisablePassport();
 174
 0175            bool needsLoading = !AvatarUtils.HaveSameWearablesAndColors(model,newModel);
 0176            model = newModel;
 177
 178#if UNITY_EDITOR
 0179            gameObject.name = $"Avatar Shape {model.GetName()}";
 180#endif
 181
 182            // To deal with the cases in which the entity transform was configured before the AvatarShape
 0183            if (!initializedPosition)
 184            {
 0185                OnEntityTransformChanged(entity.gameObject.transform.localPosition,
 186                    entity.gameObject.transform.localRotation, true);
 187            }
 188
 189            // NOTE: we subscribe here to transform changes since we might "lose" the message
 190            // if we subscribe after a any yield
 0191            entity.OnTransformChange -= OnEntityTransformChanged;
 0192            entity.OnTransformChange += OnEntityTransformChanged;
 193
 0194            var wearableItems = model.GetWereables().ToList();
 0195            wearableItems.Add(model.GetBodyShape());
 196
 197            // temporarily hardcoding the embedded emotes until the user profile provides the equipped ones
 0198            var embeddedEmotesSo = await emotesCatalog.Ref.GetEmbeddedEmotes();
 0199            wearableItems.AddRange(embeddedEmotesSo.GetAllIds());
 0200            HashSet<string> emotes = new HashSet<string>();
 0201            emotes.UnionWith(embeddedEmotesSo.GetAllIds());
 202
 0203            if (avatar.status != IAvatar.Status.Loaded || needsLoading)
 204            {
 0205                loadingCts?.Cancel();
 0206                loadingCts?.Dispose();
 0207                loadingCts = new CancellationTokenSource();
 208                try
 209                {
 0210                    await LoadAvatar(wearableItems,emotes);
 0211                }
 0212                catch (Exception e)
 213                {
 214                    // If the load of the avatar fails, we do it silently so the scene continue to operate.
 215                    // The LoadAvatar function will show the wearables but not the error itself, we need extra context
 0216                    if (e is not OperationCanceledException)
 0217                        Debug.LogException(e);
 0218                }
 219            }
 220
 0221            if (sceneEmoteHandler.IsSceneEmote(model.ExpressionTriggerId))
 0222                sceneEmoteHandler
 223                   .LoadAndPlayEmote(model.BodyShape, model.ExpressionTriggerId)
 224                   .Forget();
 225            else
 0226                avatar.GetEmotesController().PlayEmote(model.ExpressionTriggerId, model.GetExpressionTriggerTimestamp())
 227
 0228            UpdatePlayerStatus(entity, model);
 229
 0230            onPointerDown.Initialize(
 231                viewProfilePointerModel,
 232                entity, player
 233            );
 234
 0235            outlineOnHover.Initialize(entity, player.avatar);
 236
 0237            avatarCollider.gameObject.SetActive(true);
 238
 0239            EnablePassport();
 240
 0241            onPointerDown.SetColliderEnabled(isGlobalSceneAvatar);
 0242            onPointerDown.SetOnClickReportEnabled(isGlobalSceneAvatar);
 0243        }
 244
 245        private async UniTask LoadAvatar(List<string> wearableItems, HashSet<string> emotes)
 246        {
 247            try
 248            {
 249                //TODO Add Collider to the AvatarSystem
 250                //TODO Without this the collider could get triggered disabling the avatar container,
 251                // this would stop the loading process due to the underlying coroutines of the AssetLoader not starting
 0252                avatarCollider.gameObject.SetActive(false);
 253
 0254                SetImpostor(model.Id);
 0255                UserProfile profile = userProfileBridge.Get(model.Id);
 0256                playerName.SetName(model.GetName(), profile?.hasClaimedName ?? false, profile?.isGuest ?? false);
 0257                playerName.Show(true);
 0258                await avatar.Load(wearableItems, emotes.ToList(),new AvatarSettings
 259                {
 260                    playerName = model.GetName(),
 261                    bodyshapeId = model.GetBodyShape(),
 262                    eyesColor = model.GetEyeColor().ToUnityColor(),
 263                    skinColor = model.GetSkinColor().ToUnityColor(),
 264                    hairColor = model.GetHairColor().ToUnityColor(),
 265                }, loadingCts.Token);
 0266            }
 0267            catch (OperationCanceledException)
 268            {
 0269                Cleanup();
 0270                throw;
 271            }
 0272            catch (Exception e)
 273            {
 0274                Cleanup();
 0275                Debug.Log($"Avatar.Load failed with wearables:[{string.Join(",", wearableItems)}] for bodyshape:{model.B
 0276                throw;
 277            }
 278            finally
 279            {
 0280                loadingCts?.Dispose();
 0281                loadingCts = null;
 282            }
 0283        }
 284
 285        public void SetImpostor(string userId)
 286        {
 0287            currentLazyObserver?.RemoveListener(avatar.SetImpostorTexture);
 0288            if (string.IsNullOrEmpty(userId))
 0289                return;
 290
 0291            UserProfile userProfile = UserProfileController.GetProfileByUserId(userId);
 0292            if (userProfile == null)
 0293                return;
 294
 0295            currentLazyObserver = userProfile.bodySnapshotObserver;
 0296            currentLazyObserver.AddListener(avatar.SetImpostorTexture);
 0297        }
 298
 0299        private void PlayerPointerExit() { playerName?.SetForceShow(false); }
 300
 0301        private void PlayerPointerEnter() { playerName?.SetForceShow(true); }
 302
 303        internal void UpdatePlayerStatus(IDCLEntity entity, PBAvatarShape model)
 304        {
 305            // Remove the player status if the userId changes
 0306            if (player != null && (player.id != model.Id || player.name != model.GetName()))
 0307                otherPlayers.Remove(player.id);
 308
 0309            if (isGlobalSceneAvatar && string.IsNullOrEmpty(model?.GetName()))
 0310                return;
 311
 0312            bool isNew = player == null;
 0313            if (isNew)
 0314                player = new Player();
 315
 0316            bool isNameDirty = player.name != model.GetName();
 317
 0318            player.id = model.Id;
 0319            player.name = model.GetName();
 0320            player.isTalking = model.Talking;
 0321            player.worldPosition = entity.gameObject.transform.position;
 0322            player.avatar = avatar;
 0323            player.onPointerDownCollider = onPointerDown;
 0324            player.collider = avatarCollider;
 325
 0326            if (isNew)
 327            {
 0328                player.playerName = playerName;
 0329                player.playerName.Show();
 0330                player.anchorPoints = anchorPoints;
 0331                if (isGlobalSceneAvatar)
 332                {
 333                    // TODO: Note: This is having a problem, sometimes the users has been detected as new 2 times and it
 334                    // we should investigate this
 0335                    if (otherPlayers.ContainsKey(player.id))
 0336                        otherPlayers.Remove(player.id);
 0337                    otherPlayers.Add(player.id, player);
 338                }
 0339                avatarReporterController.ReportAvatarRemoved();
 340            }
 341
 0342            avatarReporterController.SetUp(entity.scene.sceneData.sceneNumber, player.id);
 343
 0344            float height = AvatarSystemUtils.AVATAR_Y_OFFSET + avatar.extents.y;
 345
 0346            anchorPoints.Prepare(avatarContainer.transform, baseAvatarReferences.Anchors, height);
 347
 0348            player.playerName.SetIsTalking(model.Talking);
 0349            player.playerName.SetYOffset(Mathf.Max(MINIMUM_PLAYERNAME_HEIGHT, height));
 350
 0351            if (isNameDirty)
 352            {
 0353                UserProfile profile = userProfileBridge.Get(model.Id);
 0354                player.playerName.SetName(model.GetName(), profile?.hasClaimedName ?? false, profile?.isGuest ?? false);
 355            }
 0356        }
 357
 358        private void Update()
 359        {
 0360            if (player == null || entity == null)
 0361                return;
 362
 0363            player.worldPosition = entity.gameObject.transform.position;
 0364            player.forwardDirection = entity.gameObject.transform.forward;
 0365            avatarReporterController.ReportAvatarPosition(player.worldPosition);
 0366        }
 367
 368        public void DisablePassport()
 369        {
 0370            if (onPointerDown.collider == null)
 0371                return;
 372
 0373            onPointerDown.SetPassportEnabled(false);
 0374        }
 375
 376        public void EnablePassport()
 377        {
 0378            if (onPointerDown.collider == null)
 0379                return;
 380
 0381            onPointerDown.SetPassportEnabled(true);
 0382        }
 383
 384        private void OnEntityTransformChanged(Vector3 newPosition, Quaternion newRotation)
 385        {
 0386            OnEntityTransformChanged(newPosition, newRotation, !initializedPosition);
 0387        }
 388
 389        private void OnEntityTransformChanged(Vector3 position, Quaternion rotation, bool inmediate)
 390        {
 0391            if (entity == null)
 0392                return;
 393
 0394            if (isGlobalSceneAvatar)
 395            {
 0396                avatarMovementController.OnTransformChanged(position, rotation, inmediate);
 397            }
 398            else
 399            {
 0400                var scenePosition = Helpers.Utils.GridToWorldPosition(entity.scene.sceneData.basePosition.x, entity.scen
 0401                avatarMovementController.OnTransformChanged(scenePosition + position, rotation, inmediate);
 402            }
 0403            initializedPosition = true;
 0404        }
 405
 406        public void OnPoolRelease()
 407        {
 0408            Cleanup();
 0409        }
 410
 411        public void OnPoolGet()
 412        {
 0413            initializedPosition = false;
 0414            model = new PBAvatarShape();
 0415            player = null;
 0416            SetPlayerNameReference();
 0417        }
 418
 419        public void ApplyHideAvatarModifier()
 420        {
 0421            avatar.AddVisibilityConstraint(IN_HIDE_AREA);
 0422            onPointerDown.gameObject.SetActive(false);
 0423            playerNameContainer.SetActive(false);
 0424        }
 425
 426        public void RemoveHideAvatarModifier()
 427        {
 0428            avatar.RemoveVisibilityConstrain(IN_HIDE_AREA);
 0429            onPointerDown.gameObject.SetActive(true);
 0430            playerNameContainer.SetActive(true);
 0431        }
 432
 433        public void Cleanup()
 434        {
 2435            initializedPosition = false;
 2436            playerName?.Hide(true);
 2437            if (player != null)
 438            {
 0439                otherPlayers.Remove(player.id);
 0440                player = null;
 441            }
 442
 2443            loadingCts?.Cancel();
 2444            loadingCts?.Dispose();
 2445            loadingCts = null;
 446
 2447            currentLazyObserver?.RemoveListener(avatar.SetImpostorTexture);
 2448            avatar.Dispose();
 449
 2450            if (poolableObject != null)
 451            {
 0452                poolableObject.OnRelease -= Cleanup;
 453            }
 454
 2455            onPointerDown.OnPointerDownReport -= PlayerClicked;
 2456            onPointerDown.OnPointerEnterReport -= PlayerPointerEnter;
 2457            onPointerDown.OnPointerExitReport -= PlayerPointerExit;
 2458            outlineOnHover.OnPointerEnterReport -= PlayerPointerEnter;
 2459            outlineOnHover.OnPointerExitReport -= PlayerPointerExit;
 460
 2461            if (entity != null)
 462            {
 0463                entity.OnTransformChange = null;
 0464                entity = null;
 465            }
 466
 2467            avatarReporterController.ReportAvatarRemoved();
 2468        }
 469
 470        [ContextMenu("Print current profile")]
 0471        private void PrintCurrentProfile() { Debug.Log(JsonUtility.ToJson(model)); }
 472    }
 473}
 474