< Summary

Class:DCL.AvatarShape
Assembly:AvatarShape
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Avatar/AvatarShape.cs
Covered lines:71
Uncovered lines:136
Coverable lines:207
Total lines:471
Line coverage:34.2% (71 of 207)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
AvatarShape()0%110100%
Awake()0%330100%
Initialize(...)0%220100%
GetStandardAvatar()0%110100%
GetAvatarWithHologram()0%110100%
Start()0%330100%
PlayerClicked()0%6200%
OnDestroy()0%5.125083.33%
ApplyChanges()0%176.3517018%
SetImpostor(...)0%20400%
PlayerPointerExit()0%6200%
PlayerPointerEnter()0%6200%
UpdatePlayerStatus(...)0%1821300%
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.249085.71%
UpdateOutOfBoundariesState(...)0%3.13077.78%
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 System;
 2using DCL.Components;
 3using DCL.Interface;
 4using System.Collections;
 5using System.Collections.Generic;
 6using System.Linq;
 7using System.Threading;
 8using AvatarSystem;
 9using DCL.Configuration;
 10using DCL.Controllers;
 11using DCL.Emotes;
 12using DCL.Helpers;
 13using DCL.Models;
 14using GPUSkinning;
 15using UnityEngine;
 16using Avatar = AvatarSystem.Avatar;
 17using LOD = AvatarSystem.LOD;
 18
 19namespace DCL
 20{
 21    public class AvatarShape : BaseComponent, IHideAvatarAreaHandler, IHidePassportAreaHandler, IOutOfSceneBoundariesHan
 22    {
 23        private const string CURRENT_PLAYER_ID = "CurrentPlayerInfoCardId";
 24        private const float MINIMUM_PLAYERNAME_HEIGHT = 2.7f;
 25        private const float AVATAR_PASSPORT_TOGGLE_ALPHA_THRESHOLD = 0.9f;
 26        private const string VISIBILITY_CONSTRAINT_HIDE_AREA = "IN_HIDE_AREA";
 27        private const string VISIBILITY_CONSTRAINT_OUTSIDE_SCENE_BOUNDS = "OUTSIDE_SCENE_BOUNDS";
 28
 29        public static event Action<IDCLEntity, AvatarShape> OnAvatarShapeUpdated;
 30
 31        public GameObject avatarContainer;
 32        public Collider avatarCollider;
 33        public AvatarMovementController avatarMovementController;
 34        public StickersController stickersControllers;
 35        [SerializeField] private Transform avatarRevealContainer;
 36        [SerializeField] private GameObject armatureContainer;
 37
 38        [SerializeField] internal AvatarOnPointerDown onPointerDown;
 39        [SerializeField] internal GameObject playerNameContainer;
 40        internal IPlayerName playerName;
 41        internal IAvatarReporterController avatarReporterController;
 42
 43        private StringVariable 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
 32752        private IAvatarAnchorPoints anchorPoints = new AvatarAnchorPoints();
 53        internal IAvatar avatar;
 32754        private readonly AvatarModel currentAvatar = new AvatarModel { wearables = new List<string>() };
 55        private CancellationTokenSource loadingCts;
 56        private ILazyTextureObserver currentLazyObserver;
 32757        private bool isGlobalSceneAvatar = true;
 58        private BaseRefCounter<AvatarModifierAreaID> currentActiveModifiers;
 59
 460        public override string componentName => "avatarShape";
 61
 62        private void Awake()
 63        {
 32564            model = new AvatarModel();
 32565            currentPlayerInfoCardId = Resources.Load<StringVariable>(CURRENT_PLAYER_ID);
 66
 32567            if (DataStore.i.avatarConfig.useHologramAvatar.Get())
 268                avatar = GetAvatarWithHologram();
 69            else
 32370                avatar = GetStandardAvatar();
 71
 32572            if (avatarReporterController == null)
 73            {
 32574                avatarReporterController = new AvatarReporterController(Environment.i.world.state);
 75            }
 32576        }
 77
 78        public override void Initialize(IParcelScene scene, IDCLEntity entity)
 79        {
 580            base.Initialize(scene, entity);
 581            DataStore.i.sceneBoundariesChecker?.Add(entity,this);
 582        }
 83
 84        private IAvatar GetStandardAvatar()
 85        {
 32386            var visibility = new Visibility();
 87
 32388            return Environment.i.serviceLocator.Get<IAvatarFactory>()
 89                              .CreateAvatar(
 90                                   avatarContainer,
 91                                   GetComponentInChildren<AvatarAnimatorLegacy>(),
 92                                   new LOD(avatarContainer, visibility, avatarMovementController),
 93                                   visibility
 94                               );
 95        }
 96
 97        private IAvatar GetAvatarWithHologram()
 98        {
 299            Visibility visibility = new Visibility();
 100
 2101            return Environment.i.serviceLocator.Get<IAvatarFactory>()
 102                              .CreateAvatarWithHologram(
 103                                   avatarContainer,
 104                                   avatarRevealContainer,
 105                                   armatureContainer,
 106                                   GetComponentInChildren<AvatarAnimatorLegacy>(),
 107                                   new LOD(avatarContainer, visibility, avatarMovementController),
 108                                   visibility
 109                               );
 110        }
 111
 112        private void Start()
 113        {
 1114            playerName = GetComponentInChildren<IPlayerName>();
 1115            playerName?.Hide(true);
 1116            currentActiveModifiers ??= new BaseRefCounter<AvatarModifierAreaID>();
 1117        }
 118
 119        private void PlayerClicked()
 120        {
 0121            if (model == null)
 0122                return;
 0123            currentPlayerInfoCardId.Set(((AvatarModel) model).id);
 0124        }
 125
 126        public void OnDestroy()
 127        {
 325128            if(entity != null)
 0129                DataStore.i.sceneBoundariesChecker?.Remove(entity,this);
 130
 325131            Cleanup();
 132
 325133            if (poolableObject != null && poolableObject.isInsidePool)
 5134                poolableObject.RemoveFromPool();
 325135        }
 136
 137        public override IEnumerator ApplyChanges(BaseModel newModel)
 138        {
 5139            isGlobalSceneAvatar = scene.sceneData.sceneNumber == EnvironmentSettings.AVATAR_GLOBAL_SCENE_NUMBER;
 140
 5141            var model = (AvatarModel) newModel;
 142
 5143            bool needsLoading = !model.HaveSameWearablesAndColors(currentAvatar);
 5144            currentAvatar.CopyFrom(model);
 145
 5146            if (string.IsNullOrEmpty(model.bodyShape) || model.wearables.Count == 0)
 1147                yield break;
 148#if UNITY_EDITOR
 4149            gameObject.name = $"Avatar Shape {model.name}";
 150#endif
 4151            everythingIsLoaded = false;
 152
 153            bool avatarDone = false;
 154            bool avatarFailed = false;
 155
 4156            yield return null; //NOTE(Brian): just in case we have a Object.Destroy waiting to be resolved.
 157
 158            // To deal with the cases in which the entity transform was configured before the AvatarShape
 0159            if (!initializedPosition && scene.componentsManagerLegacy.HasComponent(entity, CLASS_ID_COMPONENT.TRANSFORM)
 160            {
 0161                initializedPosition = true;
 0162                OnEntityTransformChanged(entity.gameObject.transform.localPosition,
 163                    entity.gameObject.transform.localRotation, true);
 164            }
 165
 166            // NOTE: we subscribe here to transform changes since we might "lose" the message
 167            // if we subscribe after a any yield
 0168            entity.OnTransformChange -= OnEntityTransformChanged;
 0169            entity.OnTransformChange += OnEntityTransformChanged;
 170
 0171            var wearableItems = model.wearables.ToList();
 0172            wearableItems.Add(model.bodyShape);
 173
 0174            if (avatar.status != IAvatar.Status.Loaded || needsLoading)
 175            {
 0176                HashSet<string> emotes = new HashSet<string>(currentAvatar.emotes.Select(x => x.urn));
 0177                var embeddedEmotesSo = Resources.Load<EmbeddedEmotesSO>("EmbeddedEmotes");
 0178                var embeddedEmoteIds = embeddedEmotesSo.emotes.Select(x => x.id);
 179                //here we add emote ids to both new and old emote loading flow to merge the results later
 180                //because some users might have emotes as wearables and others only as emotes
 0181                foreach (var emoteId in embeddedEmoteIds)
 182                {
 0183                    emotes.Add(emoteId);
 0184                    wearableItems.Add(emoteId);
 185                }
 186
 187                //TODO Add Collider to the AvatarSystem
 188                //TODO Without this the collider could get triggered disabling the avatar container,
 189                // this would stop the loading process due to the underlying coroutines of the AssetLoader not starting
 0190                avatarCollider.gameObject.SetActive(false);
 191
 0192                SetImpostor(model.id);
 0193                loadingCts?.Cancel();
 0194                loadingCts?.Dispose();
 0195                loadingCts = new CancellationTokenSource();
 0196                if (DataStore.i.avatarConfig.useHologramAvatar.Get())
 197                {
 0198                    playerName.SetName(model.name);
 0199                    playerName.Show(true);
 200                }
 201
 0202                avatar.Load(wearableItems, emotes.ToList(), new AvatarSettings
 203                {
 204                    playerName = model.name,
 205                    bodyshapeId = model.bodyShape,
 206                    eyesColor = model.eyeColor,
 207                    skinColor = model.skinColor,
 208                    hairColor = model.hairColor,
 209                }, loadingCts.Token);
 210
 211                // Yielding a UniTask doesn't do anything, we manually wait until the avatar is ready
 0212                yield return new WaitUntil(() => avatar.status == IAvatar.Status.Loaded);
 213            }
 214
 0215            avatar.PlayEmote(model.expressionTriggerId, model.expressionTriggerTimestamp);
 216
 0217            onPointerDown.OnPointerDownReport -= PlayerClicked;
 0218            onPointerDown.OnPointerDownReport += PlayerClicked;
 0219            onPointerDown.OnPointerEnterReport -= PlayerPointerEnter;
 0220            onPointerDown.OnPointerEnterReport += PlayerPointerEnter;
 0221            onPointerDown.OnPointerExitReport -= PlayerPointerExit;
 0222            onPointerDown.OnPointerExitReport += PlayerPointerExit;
 223
 0224            UpdatePlayerStatus(model);
 225
 0226            onPointerDown.Initialize(
 227                new OnPointerDown.Model()
 228                {
 229                    type = OnPointerDown.NAME,
 230                    button = WebInterface.ACTION_BUTTON.POINTER.ToString(),
 231                    hoverText = "view profile"
 232                },
 233                entity, player
 234            );
 235
 0236            avatarCollider.gameObject.SetActive(true);
 237
 0238            everythingIsLoaded = true;
 0239            OnAvatarShapeUpdated?.Invoke(entity, this);
 240
 0241            onPointerDown.SetColliderEnabled(isGlobalSceneAvatar);
 0242            onPointerDown.SetOnClickReportEnabled(isGlobalSceneAvatar);
 0243        }
 244
 245        public void SetImpostor(string userId)
 246        {
 0247            currentLazyObserver?.RemoveListener(avatar.SetImpostorTexture);
 0248            if (string.IsNullOrEmpty(userId))
 0249                return;
 250
 0251            UserProfile userProfile = UserProfileController.GetProfileByUserId(userId);
 0252            if (userProfile == null)
 0253                return;
 254
 0255            currentLazyObserver = userProfile.bodySnapshotObserver;
 0256            currentLazyObserver.AddListener(avatar.SetImpostorTexture);
 0257        }
 258
 0259        private void PlayerPointerExit() { playerName?.SetForceShow(false); }
 0260        private void PlayerPointerEnter() { playerName?.SetForceShow(true); }
 261
 262        private void UpdatePlayerStatus(AvatarModel model)
 263        {
 264            // Remove the player status if the userId changes
 0265            if (player != null && (player.id != model.id || player.name != model.name))
 0266                otherPlayers.Remove(player.id);
 267
 0268            if (isGlobalSceneAvatar && string.IsNullOrEmpty(model?.id))
 0269                return;
 270
 0271            bool isNew = player == null;
 0272            if (isNew)
 273            {
 0274                player = new Player();
 275            }
 276
 0277            bool isNameDirty = player.name != model.name;
 278
 0279            player.id = model.id;
 0280            player.name = model.name;
 0281            player.isTalking = model.talking;
 0282            player.worldPosition = entity.gameObject.transform.position;
 0283            player.avatar = avatar;
 0284            player.onPointerDownCollider = onPointerDown;
 0285            player.collider = avatarCollider;
 286
 0287            if (isNew)
 288            {
 0289                player.playerName = playerName;
 0290                player.playerName.Show();
 0291                player.anchorPoints = anchorPoints;
 0292                if (isGlobalSceneAvatar)
 293                {
 294                    // TODO: Note: This is having a problem, sometimes the users has been detected as new 2 times and it
 295                    // we should investigate this
 0296                    if (otherPlayers.ContainsKey(player.id))
 0297                        otherPlayers.Remove(player.id);
 0298                    otherPlayers.Add(player.id, player);
 299                }
 0300                avatarReporterController.ReportAvatarRemoved();
 301            }
 302
 0303            avatarReporterController.SetUp(entity.scene.sceneData.sceneNumber, player.id);
 304
 0305            float height = AvatarSystemUtils.AVATAR_Y_OFFSET + avatar.extents.y;
 306
 0307            anchorPoints.Prepare(avatarContainer.transform, avatar.GetBones(), height);
 308
 0309            player.playerName.SetIsTalking(model.talking);
 0310            player.playerName.SetYOffset(Mathf.Max(MINIMUM_PLAYERNAME_HEIGHT, height));
 0311            if (isNameDirty)
 0312                player.playerName.SetName(model.name);
 0313        }
 314
 315        private void Update()
 316        {
 1317            if (player != null)
 318            {
 0319                player.worldPosition = entity.gameObject.transform.position;
 0320                player.forwardDirection = entity.gameObject.transform.forward;
 0321                avatarReporterController.ReportAvatarPosition(player.worldPosition);
 322            }
 1323        }
 324
 325        private void OnEntityTransformChanged(object newModel)
 326        {
 0327            DCLTransform.Model newTransformModel = (DCLTransform.Model)newModel;
 0328            OnEntityTransformChanged(newTransformModel.position, newTransformModel.rotation, !initializedPosition);
 0329        }
 330
 331        private void OnEntityTransformChanged(in Vector3 position, in Quaternion rotation, bool inmediate)
 332        {
 0333            if (isGlobalSceneAvatar)
 334            {
 0335                avatarMovementController.OnTransformChanged(position, rotation, inmediate);
 336            }
 337            else
 338            {
 0339                var scenePosition = Utils.GridToWorldPosition(entity.scene.sceneData.basePosition.x, entity.scene.sceneD
 0340                avatarMovementController.OnTransformChanged(scenePosition + position, rotation, inmediate);
 341            }
 0342            initializedPosition = true;
 0343        }
 344
 345        public override void OnPoolGet()
 346        {
 5347            base.OnPoolGet();
 348
 5349            everythingIsLoaded = false;
 5350            initializedPosition = false;
 5351            model = new AvatarModel();
 5352            player = null;
 5353        }
 354
 355        public void ApplyHideAvatarModifier()
 356        {
 0357            if (!currentActiveModifiers.ContainsKey(AvatarModifierAreaID.HIDE_AVATAR))
 358            {
 0359                avatar.AddVisibilityConstraint(VISIBILITY_CONSTRAINT_HIDE_AREA);
 0360                onPointerDown.gameObject.SetActive(false);
 0361                playerNameContainer.SetActive(false);
 0362                stickersControllers.ToggleHideArea(true);
 363            }
 0364            currentActiveModifiers.AddRefCount(AvatarModifierAreaID.HIDE_AVATAR);
 0365        }
 366
 367        public void RemoveHideAvatarModifier()
 368        {
 0369            currentActiveModifiers.RemoveRefCount(AvatarModifierAreaID.HIDE_AVATAR);
 0370            if (!currentActiveModifiers.ContainsKey(AvatarModifierAreaID.HIDE_AVATAR))
 371            {
 0372                avatar.RemoveVisibilityConstrain(VISIBILITY_CONSTRAINT_HIDE_AREA);
 0373                onPointerDown.gameObject.SetActive(true);
 0374                playerNameContainer.SetActive(true);
 0375                stickersControllers.ToggleHideArea(false);
 376            }
 0377        }
 378
 379        public void ApplyHidePassportModifier()
 380        {
 0381            if (!currentActiveModifiers.ContainsKey(AvatarModifierAreaID.DISABLE_PASSPORT))
 382            {
 0383                DisablePassport();
 384            }
 0385            currentActiveModifiers.AddRefCount(AvatarModifierAreaID.DISABLE_PASSPORT);
 0386        }
 387
 388        public void RemoveHidePassportModifier()
 389        {
 0390            currentActiveModifiers.RemoveRefCount(AvatarModifierAreaID.DISABLE_PASSPORT);
 0391            if (!currentActiveModifiers.ContainsKey(AvatarModifierAreaID.DISABLE_PASSPORT))
 392            {
 0393                EnablePasssport();
 394            }
 0395        }
 396
 397        private void EnablePasssport()
 398        {
 0399            if (onPointerDown.collider == null)
 0400                return;
 401
 0402            onPointerDown.SetPassportEnabled(true);
 0403        }
 404
 405        private void DisablePassport()
 406        {
 0407            if (onPointerDown.collider == null)
 0408                return;
 409
 0410            onPointerDown.SetPassportEnabled(false);
 0411        }
 412
 413        public override void Cleanup()
 414        {
 335415            base.Cleanup();
 416
 335417            playerName?.Hide(true);
 335418            if (player != null)
 419            {
 420                // AvatarShape used from the SDK doesn't register the avatars in 'otherPlayers'
 0421                if(!string.IsNullOrEmpty(player.id))
 0422                    otherPlayers.Remove(player.id);
 0423                player = null;
 424            }
 425
 335426            loadingCts?.Cancel();
 335427            loadingCts?.Dispose();
 335428            loadingCts = null;
 335429            currentLazyObserver?.RemoveListener(avatar.SetImpostorTexture);
 335430            avatar.Dispose();
 431
 335432            if (poolableObject != null)
 433            {
 15434                poolableObject.OnRelease -= Cleanup;
 435            }
 436
 335437            onPointerDown.OnPointerDownReport -= PlayerClicked;
 335438            onPointerDown.OnPointerEnterReport -= PlayerPointerEnter;
 335439            onPointerDown.OnPointerExitReport -= PlayerPointerExit;
 440
 335441            if (entity != null)
 442            {
 5443                entity.OnTransformChange = null;
 5444                entity = null;
 445            }
 446
 335447            avatarReporterController.ReportAvatarRemoved();
 335448        }
 449
 450        public void UpdateOutOfBoundariesState(bool isInsideBoundaries)
 451        {
 1452            if (scene.isPersistent)
 0453                isInsideBoundaries = true;
 454
 1455            if(isInsideBoundaries)
 0456                avatar.RemoveVisibilityConstrain(VISIBILITY_CONSTRAINT_OUTSIDE_SCENE_BOUNDS);
 457            else
 1458                avatar.AddVisibilityConstraint(VISIBILITY_CONSTRAINT_OUTSIDE_SCENE_BOUNDS);
 459
 1460            onPointerDown.gameObject.SetActive(isInsideBoundaries);
 1461            playerNameContainer.SetActive(isInsideBoundaries);
 1462            stickersControllers.ToggleHideArea(!isInsideBoundaries);
 1463        }
 464
 1465        public override int GetClassId() { return (int) CLASS_ID_COMPONENT.AVATAR_SHAPE; }
 466
 467        [ContextMenu("Print current profile")]
 0468        private void PrintCurrentProfile() { Debug.Log(JsonUtility.ToJson(model)); }
 469
 470    }
 471}