< Summary

Class:DCL.AvatarShape
Assembly:AvatarShape
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Avatar/AvatarShape.cs
Covered lines:63
Uncovered lines:164
Coverable lines:227
Total lines:525
Line coverage:27.7% (63 of 227)
Covered branches:0
Total branches:0
Covered methods:12
Total methods:29
Method coverage:41.3% (12 of 29)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
AvatarShape()0%110100%
Awake()0%3.013090.91%
Initialize(...)0%220100%
GetStandardAvatar()0%110100%
GetAvatarWithHologram()0%6200%
Start()0%330100%
PlayerClicked()0%6200%
OnDestroy()0%5.125083.33%
ApplyChanges()0%564.132709.68%
SetImpostor(...)0%20400%
PlayerPointerExit()0%6200%
PlayerPointerEnter()0%6200%
UpdatePlayerStatus(...)0%3061700%
Update()0%2.862040%
OnEntityTransformChanged(...)0%2100%
OnEntityTransformChanged(...)0%6200%
OnPoolGet()0%110100%
ApplyHideAvatarModifier()0%6200%
RemoveHideAvatarModifier()0%6200%
ApplyHidePassportModifier()0%6200%
RemoveHidePassportModifier()0%6200%
EnablePasssport()0%6200%
DisablePassport()0%6200%
Cleanup()0%9.169087.5%
UpdateOutOfBoundariesState(...)0%12300%
GetClassId()0%110100%
PrintCurrentProfile()0%2100%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Avatar/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;
 12using System.Collections.Generic;
 13using System.Linq;
 14using System.Threading;
 15using UnityEngine;
 16using LOD = AvatarSystem.LOD;
 17
 18namespace DCL
 19{
 20    public class AvatarShape : BaseComponent, IHideAvatarAreaHandler, IHidePassportAreaHandler, IOutOfSceneBoundariesHan
 21    {
 22        private const float MINIMUM_PLAYERNAME_HEIGHT = 2.7f;
 23        private const string VISIBILITY_CONSTRAINT_HIDE_AREA = "IN_HIDE_AREA";
 24        private const string VISIBILITY_CONSTRAINT_OUTSIDE_SCENE_BOUNDS = "OUTSIDE_SCENE_BOUNDS";
 25        private const string OPEN_PASSPORT_SOURCE = "World";
 26
 27        public static event Action<IDCLEntity, AvatarShape> OnAvatarShapeUpdated;
 28
 29        public GameObject avatarContainer;
 30        public Collider avatarCollider;
 31        public AvatarMovementController avatarMovementController;
 32        public StickersController stickersControllers;
 33
 34        [SerializeField] internal AvatarOnPointerDown onPointerDown;
 35        [SerializeField] internal AvatarOutlineOnHoverEvent outlineOnHover;
 36        [SerializeField] internal GameObject playerNameContainer;
 37        [SerializeField] private Transform baseAvatarContainer;
 38        [SerializeField] internal BaseAvatarReferences baseAvatarReferencesPrefab;
 39
 40        internal IPlayerName playerName;
 41        internal IAvatarReporterController avatarReporterController;
 42
 43        private BaseVariable<(string playerId, string source)> currentPlayerInfoCardId;
 44
 45        public bool everythingIsLoaded;
 46
 47        bool initializedPosition = false;
 48
 49        private Player player = null;
 050        private BaseDictionary<string, Player> otherPlayers => DataStore.i.player.otherPlayers;
 51
 31152        private IAvatarAnchorPoints anchorPoints = new AvatarAnchorPoints();
 53        internal IAvatar avatar;
 31154        private readonly AvatarModel currentAvatar = new AvatarModel { wearables = new List<string>() };
 55        private CancellationTokenSource loadingCts;
 56        private ILazyTextureObserver currentLazyObserver;
 31157        private bool isGlobalSceneAvatar = true;
 58        private BaseRefCounter<AvatarModifierAreaID> currentActiveModifiers;
 59        private IUserProfileBridge userProfileBridge;
 60        private Service<IEmotesCatalogService> emotesCatalogService;
 461        public override string componentName => "avatarShape";
 62        private AvatarSceneEmoteHandler sceneEmoteHandler;
 63        private IAvatarEmotesController emotesController;
 64        private IBaseAvatarReferences baseAvatarReferences;
 31165        private readonly OnPointerEvent.Model pointerEventModel = new ()
 66        {
 67            type = OnPointerDown.NAME,
 68            button = WebInterface.ACTION_BUTTON.POINTER.ToString(),
 69            hoverText = "View Profile",
 70        };
 71
 72        private void Awake()
 73        {
 30874            model = new AvatarModel();
 30875            currentPlayerInfoCardId = DataStore.i.HUDs.currentPlayerId;
 76
 77            // TODO: user profile bridge should be retrieved from the service locator
 30878            userProfileBridge = new UserProfileWebInterfaceBridge();
 79
 30880            if (DataStore.i.avatarConfig.useHologramAvatar.Get())
 081                avatar = GetAvatarWithHologram();
 82            else
 30883                avatar = GetStandardAvatar();
 84
 30885            emotesController = avatar.GetEmotesController();
 86
 30887            sceneEmoteHandler = new AvatarSceneEmoteHandler(
 88                emotesController,
 89                Environment.i.serviceLocator.Get<IEmotesService>());
 90
 61691            if (avatarReporterController == null) { avatarReporterController = new AvatarReporterController(Environment.
 30892        }
 93
 94        public override void Initialize(IParcelScene scene, IDCLEntity entity)
 95        {
 196            base.Initialize(scene, entity);
 197            DataStore.i.sceneBoundariesChecker?.Add(entity, this);
 198        }
 99
 100        private IAvatar GetStandardAvatar()
 101        {
 308102            var visibility = new Visibility();
 103
 308104            return Environment.i.serviceLocator.Get<IAvatarFactory>()
 105                              .CreateAvatar(
 106                                   avatarContainer,
 107                                   GetComponentInChildren<AvatarAnimatorLegacy>(),
 108                                   new LOD(avatarContainer, visibility, avatarMovementController),
 109                                   visibility
 110                               );
 111        }
 112
 113        private IAvatar GetAvatarWithHologram()
 114        {
 0115            Visibility visibility = new Visibility();
 116
 117            // Due to how we set our pools (and how the objets are cloned in), we might find that the original item alre
 0118            baseAvatarReferences = baseAvatarContainer.GetComponentInChildren<IBaseAvatarReferences>() ?? Instantiate(ba
 119
 0120            return Environment.i.serviceLocator.Get<IAvatarFactory>()
 121                              .CreateAvatarWithHologram(
 122                                   avatarContainer,
 123                                   new BaseAvatar(baseAvatarReferences),
 124                                   GetComponentInChildren<AvatarAnimatorLegacy>(),
 125                                   new LOD(avatarContainer, visibility, avatarMovementController),
 126                                   visibility
 127                               );
 128        }
 129
 130        private void Start()
 131        {
 1132            playerName = GetComponentInChildren<IPlayerName>();
 1133            playerName?.Hide(true);
 1134            currentActiveModifiers ??= new BaseRefCounter<AvatarModifierAreaID>();
 1135        }
 136
 137        private void PlayerClicked()
 138        {
 0139            if (model == null)
 0140                return;
 141
 0142            currentPlayerInfoCardId.Set((((AvatarModel)model).id, OPEN_PASSPORT_SOURCE));
 0143        }
 144
 145        public void OnDestroy()
 146        {
 308147            if (entity != null)
 0148                DataStore.i.sceneBoundariesChecker?.Remove(entity, this);
 149
 308150            Cleanup();
 151
 308152            if (poolableObject != null && poolableObject.isInsidePool)
 1153                poolableObject.RemoveFromPool();
 308154        }
 155
 156        public override IEnumerator ApplyChanges(BaseModel newModel)
 157        {
 1158            isGlobalSceneAvatar = scene.sceneData.sceneNumber == EnvironmentSettings.AVATAR_GLOBAL_SCENE_NUMBER;
 159
 1160            var model = (AvatarModel)newModel;
 1161            bool needsLoading = !model.HaveSameWearablesAndColors(currentAvatar);
 1162            currentAvatar.CopyFrom(model);
 163
 1164            if (string.IsNullOrEmpty(model.bodyShape) || model.wearables.Count == 0)
 1165                yield break;
 166#if UNITY_EDITOR
 0167            gameObject.name = $"Avatar Shape {model.name}";
 168#endif
 0169            everythingIsLoaded = false;
 170
 171            bool avatarDone = false;
 172            bool avatarFailed = false;
 173
 0174            yield return null; //NOTE(Brian): just in case we have a Object.Destroy waiting to be resolved.
 175
 176            // To deal with the cases in which the entity transform was configured before the AvatarShape
 0177            if (!initializedPosition && scene.componentsManagerLegacy.HasComponent(entity, CLASS_ID_COMPONENT.TRANSFORM)
 178            {
 0179                initializedPosition = true;
 180
 0181                OnEntityTransformChanged(entity.gameObject.transform.localPosition,
 182                    entity.gameObject.transform.localRotation, true);
 183            }
 184
 185            // NOTE: we subscribe here to transform changes since we might "lose" the message
 186            // if we subscribe after a any yield
 0187            entity.OnTransformChange -= OnEntityTransformChanged;
 0188            entity.OnTransformChange += OnEntityTransformChanged;
 189
 0190            var wearableItems = model.wearables.ToList();
 0191            wearableItems.Add(model.bodyShape);
 192
 0193            if (avatar.status != IAvatar.Status.Loaded || needsLoading)
 194            {
 0195                HashSet<string> emotes = new HashSet<string>(currentAvatar.emotes.Select(x => x.urn));
 0196                UniTask<EmbeddedEmotesSO>.Awaiter embeddedEmotesTask = emotesCatalogService.Ref.GetEmbeddedEmotes().GetA
 0197                yield return new WaitUntil(() => embeddedEmotesTask.IsCompleted);
 0198                var embeddedEmoteIds = embeddedEmotesTask.GetResult().GetAllIds();
 199
 200                //here we add emote ids to both new and old emote loading flow to merge the results later
 201                //because some users might have emotes as wearables and others only as emotes
 0202                foreach (var emoteId in embeddedEmoteIds)
 203                {
 0204                    emotes.Add(emoteId);
 0205                    wearableItems.Add(emoteId);
 206                }
 207
 208                //TODO Add Collider to the AvatarSystem
 209                //TODO Without this the collider could get triggered disabling the avatar container,
 210                // this would stop the loading process due to the underlying coroutines of the AssetLoader not starting
 0211                avatarCollider.gameObject.SetActive(false);
 212
 0213                SetImpostor(model.id);
 0214                loadingCts?.Cancel();
 0215                loadingCts?.Dispose();
 0216                loadingCts = new CancellationTokenSource();
 217
 0218                if (DataStore.i.avatarConfig.useHologramAvatar.Get())
 219                {
 0220                    UserProfile profile = userProfileBridge.Get(model.id);
 0221                    playerName.SetName(model.name, profile?.hasClaimedName ?? false, profile?.isGuest ?? false);
 0222                    playerName.Show(true);
 223                }
 224
 0225                var forceRender = new HashSet<string>(userProfileBridge.Get(model.id)?.avatar.forceRender ?? new HashSet
 226
 0227                var avatarSettings = new AvatarSettings
 228                {
 229                    playerName = model.name,
 230                    bodyshapeId = model.bodyShape,
 231                    eyesColor = model.eyeColor,
 232                    skinColor = model.skinColor,
 233                    hairColor = model.hairColor,
 234                    forceRender = forceRender,
 235                };
 236
 0237                yield return avatar.Load(wearableItems, emotes.ToList(), avatarSettings, loadingCts.Token)
 238                                   .ToCoroutine(e =>
 239                                    {
 0240                                        if (e is not OperationCanceledException)
 0241                                            throw e;
 0242                                    });
 0243            }
 244
 0245            sceneEmoteHandler.SetExpressionLamportTimestamp(model.expressionTriggerTimestamp);
 246
 0247            if (sceneEmoteHandler.IsSceneEmote(model.expressionTriggerId))
 0248                sceneEmoteHandler
 249                   .LoadAndPlayEmote(model.bodyShape, model.expressionTriggerId)
 250                   .Forget();
 0251            else { avatar.GetEmotesController().PlayEmote(model.expressionTriggerId, model.expressionTriggerTimestamp); 
 252
 0253            onPointerDown.OnPointerDownReport -= PlayerClicked;
 0254            onPointerDown.OnPointerDownReport += PlayerClicked;
 0255            onPointerDown.OnPointerEnterReport -= PlayerPointerEnter;
 0256            onPointerDown.OnPointerEnterReport += PlayerPointerEnter;
 0257            onPointerDown.OnPointerExitReport -= PlayerPointerExit;
 0258            onPointerDown.OnPointerExitReport += PlayerPointerExit;
 259
 0260            outlineOnHover.OnPointerEnterReport -= PlayerPointerEnter;
 0261            outlineOnHover.OnPointerEnterReport += PlayerPointerEnter;
 0262            outlineOnHover.OnPointerExitReport -= PlayerPointerExit;
 0263            outlineOnHover.OnPointerExitReport += PlayerPointerExit;
 264
 0265            UpdatePlayerStatus(model);
 266
 0267            onPointerDown.Initialize(
 268                pointerEventModel,
 269                entity, player
 270            );
 271
 0272            outlineOnHover.Initialize(entity, player?.avatar);
 273
 0274            avatarCollider.gameObject.SetActive(true);
 275
 0276            everythingIsLoaded = true;
 0277            OnAvatarShapeUpdated?.Invoke(entity, this);
 278
 0279            onPointerDown.SetColliderEnabled(isGlobalSceneAvatar);
 0280            onPointerDown.SetOnClickReportEnabled(isGlobalSceneAvatar);
 0281        }
 282
 283        public void SetImpostor(string userId)
 284        {
 0285            currentLazyObserver?.RemoveListener(avatar.SetImpostorTexture);
 286
 0287            if (string.IsNullOrEmpty(userId))
 0288                return;
 289
 0290            UserProfile userProfile = UserProfileController.GetProfileByUserId(userId);
 291
 0292            if (userProfile == null)
 0293                return;
 294
 0295            currentLazyObserver = userProfile.bodySnapshotObserver;
 0296            currentLazyObserver.AddListener(avatar.SetImpostorTexture);
 0297        }
 298
 299        private void PlayerPointerExit()
 300        {
 0301            playerName?.SetForceShow(false);
 0302        }
 303
 304        private void PlayerPointerEnter()
 305        {
 0306            playerName?.SetForceShow(true);
 0307        }
 308
 309        private void UpdatePlayerStatus(AvatarModel model)
 310        {
 311            // Remove the player status if the userId changes
 0312            if (player != null && (player.id != model.id || player.name != model.name))
 0313                otherPlayers.Remove(player.id);
 314
 0315            if (isGlobalSceneAvatar && string.IsNullOrEmpty(model?.id))
 0316                return;
 317
 0318            bool isNew = player == null;
 319
 0320            if (isNew) { player = new Player(); }
 321
 0322            bool isNameDirty = player.name != model.name;
 323
 0324            player.id = model.id;
 0325            player.name = model.name;
 0326            player.isTalking = model.talking;
 0327            player.worldPosition = entity.gameObject.transform.position;
 0328            player.avatar = avatar;
 0329            player.onPointerDownCollider = onPointerDown;
 0330            player.collider = avatarCollider;
 331
 0332            if (isNew)
 333            {
 0334                player.playerName = playerName;
 0335                player.playerName.Show();
 0336                player.anchorPoints = anchorPoints;
 337
 0338                if (isGlobalSceneAvatar)
 339                {
 340                    // TODO: Note: This is having a problem, sometimes the users has been detected as new 2 times and it
 341                    // we should investigate this
 0342                    if (otherPlayers.ContainsKey(player.id))
 0343                        otherPlayers.Remove(player.id);
 344
 0345                    otherPlayers.Add(player.id, player);
 346                }
 347
 0348                avatarReporterController.ReportAvatarRemoved();
 349            }
 350
 0351            avatarReporterController.SetUp(entity.scene.sceneData.sceneNumber, player.id);
 352
 0353            float height = AvatarSystemUtils.AVATAR_Y_OFFSET + avatar.extents.y;
 354
 0355            anchorPoints.Prepare(avatarContainer.transform, baseAvatarReferences.Anchors, height);
 356
 0357            player.playerName.SetIsTalking(model.talking);
 0358            player.playerName.SetYOffset(Mathf.Max(MINIMUM_PLAYERNAME_HEIGHT, height));
 359
 0360            if (isNameDirty)
 361            {
 0362                UserProfile profile = userProfileBridge.Get(model.id);
 0363                player.playerName.SetName(model.name, profile?.hasClaimedName ?? false, profile?.isGuest ?? false);
 364            }
 0365        }
 366
 367        private void Update()
 368        {
 1369            if (player != null)
 370            {
 0371                player.worldPosition = entity.gameObject.transform.position;
 0372                player.forwardDirection = entity.gameObject.transform.forward;
 0373                avatarReporterController.ReportAvatarPosition(player.worldPosition);
 374            }
 1375        }
 376
 377        private void OnEntityTransformChanged(Vector3 newPosition, Quaternion newRotation)
 378        {
 0379            OnEntityTransformChanged(newPosition, newRotation, !initializedPosition);
 0380        }
 381
 382        private void OnEntityTransformChanged(in Vector3 position, in Quaternion rotation, bool inmediate)
 383        {
 0384            if (isGlobalSceneAvatar) { avatarMovementController.OnTransformChanged(position, rotation, inmediate); }
 385            else
 386            {
 0387                var scenePosition = Utils.GridToWorldPosition(entity.scene.sceneData.basePosition.x, entity.scene.sceneD
 0388                avatarMovementController.OnTransformChanged(scenePosition + position, rotation, inmediate);
 389            }
 390
 0391            initializedPosition = true;
 0392        }
 393
 394        public override void OnPoolGet()
 395        {
 1396            base.OnPoolGet();
 397
 1398            everythingIsLoaded = false;
 1399            initializedPosition = false;
 1400            model = new AvatarModel();
 1401            player = null;
 1402        }
 403
 404        public void ApplyHideAvatarModifier()
 405        {
 0406            if (!currentActiveModifiers.ContainsKey(AvatarModifierAreaID.HIDE_AVATAR))
 407            {
 0408                avatar.AddVisibilityConstraint(VISIBILITY_CONSTRAINT_HIDE_AREA);
 0409                onPointerDown.gameObject.SetActive(false);
 0410                playerNameContainer.SetActive(false);
 0411                stickersControllers.ToggleHideArea(true);
 412            }
 413
 0414            currentActiveModifiers.AddRefCount(AvatarModifierAreaID.HIDE_AVATAR);
 0415        }
 416
 417        public void RemoveHideAvatarModifier()
 418        {
 0419            currentActiveModifiers.RemoveRefCount(AvatarModifierAreaID.HIDE_AVATAR);
 420
 0421            if (!currentActiveModifiers.ContainsKey(AvatarModifierAreaID.HIDE_AVATAR))
 422            {
 0423                avatar.RemoveVisibilityConstrain(VISIBILITY_CONSTRAINT_HIDE_AREA);
 0424                onPointerDown.gameObject.SetActive(true);
 0425                playerNameContainer.SetActive(true);
 0426                stickersControllers.ToggleHideArea(false);
 427            }
 0428        }
 429
 430        public void ApplyHidePassportModifier()
 431        {
 0432            if (!currentActiveModifiers.ContainsKey(AvatarModifierAreaID.DISABLE_PASSPORT)) { DisablePassport(); }
 433
 0434            currentActiveModifiers.AddRefCount(AvatarModifierAreaID.DISABLE_PASSPORT);
 0435        }
 436
 437        public void RemoveHidePassportModifier()
 438        {
 0439            currentActiveModifiers.RemoveRefCount(AvatarModifierAreaID.DISABLE_PASSPORT);
 440
 0441            if (!currentActiveModifiers.ContainsKey(AvatarModifierAreaID.DISABLE_PASSPORT)) { EnablePasssport(); }
 0442        }
 443
 444        private void EnablePasssport()
 445        {
 0446            if (onPointerDown.collider == null)
 0447                return;
 448
 0449            onPointerDown.SetPassportEnabled(true);
 0450        }
 451
 452        private void DisablePassport()
 453        {
 0454            if (onPointerDown.collider == null)
 0455                return;
 456
 0457            onPointerDown.SetPassportEnabled(false);
 0458        }
 459
 460        public override void Cleanup()
 461        {
 310462            base.Cleanup();
 463
 310464            playerName?.Hide(true);
 465
 310466            if (player != null)
 467            {
 468                // AvatarShape used from the SDK doesn't register the avatars in 'otherPlayers'
 0469                if (!string.IsNullOrEmpty(player.id))
 0470                    otherPlayers.Remove(player.id);
 471
 0472                player = null;
 473            }
 474
 310475            loadingCts?.Cancel();
 310476            loadingCts?.Dispose();
 310477            loadingCts = null;
 310478            currentLazyObserver?.RemoveListener(avatar.SetImpostorTexture);
 310479            avatar.Dispose();
 480
 313481            if (poolableObject != null) { poolableObject.OnRelease -= Cleanup; }
 482
 310483            onPointerDown.OnPointerDownReport -= PlayerClicked;
 310484            onPointerDown.OnPointerEnterReport -= PlayerPointerEnter;
 310485            onPointerDown.OnPointerExitReport -= PlayerPointerExit;
 310486            outlineOnHover.OnPointerEnterReport -= PlayerPointerEnter;
 310487            outlineOnHover.OnPointerExitReport -= PlayerPointerExit;
 488
 310489            if (entity != null)
 490            {
 1491                entity.OnTransformChange = null;
 1492                entity = null;
 493            }
 494
 310495            avatarReporterController.ReportAvatarRemoved();
 310496            sceneEmoteHandler.CleanUp();
 310497        }
 498
 499        public void UpdateOutOfBoundariesState(bool isInsideBoundaries)
 500        {
 0501            if (scene.isPersistent)
 0502                isInsideBoundaries = true;
 503
 0504            if (isInsideBoundaries)
 0505                avatar.RemoveVisibilityConstrain(VISIBILITY_CONSTRAINT_OUTSIDE_SCENE_BOUNDS);
 506            else
 0507                avatar.AddVisibilityConstraint(VISIBILITY_CONSTRAINT_OUTSIDE_SCENE_BOUNDS);
 508
 0509            onPointerDown.gameObject.SetActive(isInsideBoundaries);
 0510            playerNameContainer.SetActive(isInsideBoundaries);
 0511            stickersControllers.ToggleHideArea(!isInsideBoundaries);
 0512        }
 513
 514        public override int GetClassId()
 515        {
 1516            return (int)CLASS_ID_COMPONENT.AVATAR_SHAPE;
 517        }
 518
 519        [ContextMenu("Print current profile")]
 520        private void PrintCurrentProfile()
 521        {
 0522            Debug.Log(JsonUtility.ToJson(model));
 0523        }
 524    }
 525}