< Summary

Class:DCL.AvatarShape
Assembly:AvatarShape
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Avatar/AvatarShape.cs
Covered lines:74
Uncovered lines:145
Coverable lines:219
Total lines:494
Line coverage:33.7% (74 of 219)
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%281.7321016.07%
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.189086.96%
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 UnityEngine;
 15using LOD = AvatarSystem.LOD;
 16
 17namespace DCL
 18{
 19    public class AvatarShape : BaseComponent, IHideAvatarAreaHandler, IHidePassportAreaHandler, IOutOfSceneBoundariesHan
 20    {
 21        private const string CURRENT_PLAYER_ID = "CurrentPlayerInfoCardId";
 22        private const float MINIMUM_PLAYERNAME_HEIGHT = 2.7f;
 23        private const float AVATAR_PASSPORT_TOGGLE_ALPHA_THRESHOLD = 0.9f;
 24        private const string VISIBILITY_CONSTRAINT_HIDE_AREA = "IN_HIDE_AREA";
 25        private const string VISIBILITY_CONSTRAINT_OUTSIDE_SCENE_BOUNDS = "OUTSIDE_SCENE_BOUNDS";
 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        [SerializeField] private Transform avatarRevealContainer;
 34        [SerializeField] private GameObject armatureContainer;
 35
 36        [SerializeField] internal AvatarOnPointerDown onPointerDown;
 37        [SerializeField] internal AvatarOutlineOnHoverEvent outlineOnHover;
 38        [SerializeField] internal GameObject playerNameContainer;
 39        internal IPlayerName playerName;
 40        internal IAvatarReporterController avatarReporterController;
 41
 42        private StringVariable currentPlayerInfoCardId;
 43
 44        public bool everythingIsLoaded;
 45
 46        bool initializedPosition = false;
 47
 48        private Player player = null;
 049        private BaseDictionary<string, Player> otherPlayers => DataStore.i.player.otherPlayers;
 50
 32851        private IAvatarAnchorPoints anchorPoints = new AvatarAnchorPoints();
 52        internal IAvatar avatar;
 32853        private readonly AvatarModel currentAvatar = new AvatarModel { wearables = new List<string>() };
 54        private CancellationTokenSource loadingCts;
 55        private ILazyTextureObserver currentLazyObserver;
 32856        private bool isGlobalSceneAvatar = true;
 57        private BaseRefCounter<AvatarModifierAreaID> currentActiveModifiers;
 58        private IUserProfileBridge userProfileBridge;
 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            // TODO: user profile bridge should be retrieved from the service locator
 32567            userProfileBridge = new UserProfileWebInterfaceBridge();
 68
 32569            if (DataStore.i.avatarConfig.useHologramAvatar.Get())
 270                avatar = GetAvatarWithHologram();
 71            else
 32372                avatar = GetStandardAvatar();
 73
 32574            if (avatarReporterController == null)
 75            {
 32576                avatarReporterController = new AvatarReporterController(Environment.i.world.state);
 77            }
 32578        }
 79
 80        public override void Initialize(IParcelScene scene, IDCLEntity entity)
 81        {
 582            base.Initialize(scene, entity);
 583            DataStore.i.sceneBoundariesChecker?.Add(entity,this);
 584        }
 85
 86        private IAvatar GetStandardAvatar()
 87        {
 32388            var visibility = new Visibility();
 89
 32390            return Environment.i.serviceLocator.Get<IAvatarFactory>()
 91                              .CreateAvatar(
 92                                   avatarContainer,
 93                                   GetComponentInChildren<AvatarAnimatorLegacy>(),
 94                                   new LOD(avatarContainer, visibility, avatarMovementController),
 95                                   visibility
 96                               );
 97        }
 98
 99        private IAvatar GetAvatarWithHologram()
 100        {
 2101            Visibility visibility = new Visibility();
 102
 2103            return Environment.i.serviceLocator.Get<IAvatarFactory>()
 104                              .CreateAvatarWithHologram(
 105                                   avatarContainer,
 106                                   avatarRevealContainer,
 107                                   armatureContainer,
 108                                   GetComponentInChildren<AvatarAnimatorLegacy>(),
 109                                   new LOD(avatarContainer, visibility, avatarMovementController),
 110                                   visibility
 111                               );
 112        }
 113
 114        private void Start()
 115        {
 1116            playerName = GetComponentInChildren<IPlayerName>();
 1117            playerName?.Hide(true);
 1118            currentActiveModifiers ??= new BaseRefCounter<AvatarModifierAreaID>();
 1119        }
 120
 121        private void PlayerClicked()
 122        {
 0123            if (model == null)
 0124                return;
 0125            currentPlayerInfoCardId.Set(((AvatarModel) model).id);
 0126        }
 127
 128        public void OnDestroy()
 129        {
 325130            if(entity != null)
 0131                DataStore.i.sceneBoundariesChecker?.Remove(entity,this);
 132
 325133            Cleanup();
 134
 325135            if (poolableObject != null && poolableObject.isInsidePool)
 5136                poolableObject.RemoveFromPool();
 325137        }
 138
 139        public override IEnumerator ApplyChanges(BaseModel newModel)
 140        {
 5141            isGlobalSceneAvatar = scene.sceneData.sceneNumber == EnvironmentSettings.AVATAR_GLOBAL_SCENE_NUMBER;
 142
 5143            var model = (AvatarModel) newModel;
 144
 5145            bool needsLoading = !model.HaveSameWearablesAndColors(currentAvatar);
 5146            currentAvatar.CopyFrom(model);
 147
 5148            if (string.IsNullOrEmpty(model.bodyShape) || model.wearables.Count == 0)
 1149                yield break;
 150#if UNITY_EDITOR
 4151            gameObject.name = $"Avatar Shape {model.name}";
 152#endif
 4153            everythingIsLoaded = false;
 154
 155            bool avatarDone = false;
 156            bool avatarFailed = false;
 157
 4158            yield return null; //NOTE(Brian): just in case we have a Object.Destroy waiting to be resolved.
 159
 160            // To deal with the cases in which the entity transform was configured before the AvatarShape
 0161            if (!initializedPosition && scene.componentsManagerLegacy.HasComponent(entity, CLASS_ID_COMPONENT.TRANSFORM)
 162            {
 0163                initializedPosition = true;
 0164                OnEntityTransformChanged(entity.gameObject.transform.localPosition,
 165                    entity.gameObject.transform.localRotation, true);
 166            }
 167
 168            // NOTE: we subscribe here to transform changes since we might "lose" the message
 169            // if we subscribe after a any yield
 0170            entity.OnTransformChange -= OnEntityTransformChanged;
 0171            entity.OnTransformChange += OnEntityTransformChanged;
 172
 0173            var wearableItems = model.wearables.ToList();
 0174            wearableItems.Add(model.bodyShape);
 175
 0176            if (avatar.status != IAvatar.Status.Loaded || needsLoading)
 177            {
 0178                HashSet<string> emotes = new HashSet<string>(currentAvatar.emotes.Select(x => x.urn));
 0179                var embeddedEmotesSo = Resources.Load<EmbeddedEmotesSO>("EmbeddedEmotes");
 0180                var embeddedEmoteIds = embeddedEmotesSo.emotes.Select(x => x.id);
 181                //here we add emote ids to both new and old emote loading flow to merge the results later
 182                //because some users might have emotes as wearables and others only as emotes
 0183                foreach (var emoteId in embeddedEmoteIds)
 184                {
 0185                    emotes.Add(emoteId);
 0186                    wearableItems.Add(emoteId);
 187                }
 188
 189                //TODO Add Collider to the AvatarSystem
 190                //TODO Without this the collider could get triggered disabling the avatar container,
 191                // this would stop the loading process due to the underlying coroutines of the AssetLoader not starting
 0192                avatarCollider.gameObject.SetActive(false);
 193
 0194                SetImpostor(model.id);
 0195                loadingCts?.Cancel();
 0196                loadingCts?.Dispose();
 0197                loadingCts = new CancellationTokenSource();
 0198                if (DataStore.i.avatarConfig.useHologramAvatar.Get())
 199                {
 0200                    UserProfile profile = userProfileBridge.Get(model.id);
 0201                    playerName.SetName(model.name, profile?.hasClaimedName ?? false, profile?.isGuest ?? false);
 0202                    playerName.Show(true);
 203                }
 204
 0205                avatar.Load(wearableItems, emotes.ToList(), new AvatarSettings
 206                {
 207                    playerName = model.name,
 208                    bodyshapeId = model.bodyShape,
 209                    eyesColor = model.eyeColor,
 210                    skinColor = model.skinColor,
 211                    hairColor = model.hairColor,
 212                }, loadingCts.Token);
 213
 214                // Yielding a UniTask doesn't do anything, we manually wait until the avatar is ready
 0215                yield return new WaitUntil(() => avatar.status == IAvatar.Status.Loaded);
 216            }
 217
 0218            avatar.PlayEmote(model.expressionTriggerId, model.expressionTriggerTimestamp);
 219
 0220            onPointerDown.OnPointerDownReport -= PlayerClicked;
 0221            onPointerDown.OnPointerDownReport += PlayerClicked;
 0222            onPointerDown.OnPointerEnterReport -= PlayerPointerEnter;
 0223            onPointerDown.OnPointerEnterReport += PlayerPointerEnter;
 0224            onPointerDown.OnPointerExitReport -= PlayerPointerExit;
 0225            onPointerDown.OnPointerExitReport += PlayerPointerExit;
 226
 0227            outlineOnHover.OnPointerEnterReport -= PlayerPointerEnter;
 0228            outlineOnHover.OnPointerEnterReport += PlayerPointerEnter;
 0229            outlineOnHover.OnPointerExitReport -= PlayerPointerExit;
 0230            outlineOnHover.OnPointerExitReport += PlayerPointerExit;
 231
 0232            UpdatePlayerStatus(model);
 233
 0234            onPointerDown.Initialize(
 235                new OnPointerDown.Model()
 236                {
 237                    type = OnPointerDown.NAME,
 238                    button = WebInterface.ACTION_BUTTON.POINTER.ToString(),
 239                    hoverText = "View Profile"
 240                },
 241                entity, player
 242            );
 243
 0244            outlineOnHover.Initialize(new OnPointerDown.Model(), entity, player.avatar);
 245
 0246            avatarCollider.gameObject.SetActive(true);
 247
 0248            everythingIsLoaded = true;
 0249            OnAvatarShapeUpdated?.Invoke(entity, this);
 250
 0251            onPointerDown.SetColliderEnabled(isGlobalSceneAvatar);
 0252            onPointerDown.SetOnClickReportEnabled(isGlobalSceneAvatar);
 0253        }
 254
 255        public void SetImpostor(string userId)
 256        {
 0257            currentLazyObserver?.RemoveListener(avatar.SetImpostorTexture);
 0258            if (string.IsNullOrEmpty(userId))
 0259                return;
 260
 0261            UserProfile userProfile = UserProfileController.GetProfileByUserId(userId);
 0262            if (userProfile == null)
 0263                return;
 264
 0265            currentLazyObserver = userProfile.bodySnapshotObserver;
 0266            currentLazyObserver.AddListener(avatar.SetImpostorTexture);
 0267        }
 268
 269        private void PlayerPointerExit()
 270        {
 0271            playerName?.SetForceShow(false);
 0272        }
 273
 274        private void PlayerPointerEnter()
 275        {
 0276            playerName?.SetForceShow(true);
 0277        }
 278
 279        private void UpdatePlayerStatus(AvatarModel model)
 280        {
 281            // Remove the player status if the userId changes
 0282            if (player != null && (player.id != model.id || player.name != model.name))
 0283                otherPlayers.Remove(player.id);
 284
 0285            if (isGlobalSceneAvatar && string.IsNullOrEmpty(model?.id))
 0286                return;
 287
 0288            bool isNew = player == null;
 0289            if (isNew)
 290            {
 0291                player = new Player();
 292            }
 293
 0294            bool isNameDirty = player.name != model.name;
 295
 0296            player.id = model.id;
 0297            player.name = model.name;
 0298            player.isTalking = model.talking;
 0299            player.worldPosition = entity.gameObject.transform.position;
 0300            player.avatar = avatar;
 0301            player.onPointerDownCollider = onPointerDown;
 0302            player.collider = avatarCollider;
 303
 0304            if (isNew)
 305            {
 0306                player.playerName = playerName;
 0307                player.playerName.Show();
 0308                player.anchorPoints = anchorPoints;
 0309                if (isGlobalSceneAvatar)
 310                {
 311                    // TODO: Note: This is having a problem, sometimes the users has been detected as new 2 times and it
 312                    // we should investigate this
 0313                    if (otherPlayers.ContainsKey(player.id))
 0314                        otherPlayers.Remove(player.id);
 0315                    otherPlayers.Add(player.id, player);
 316                }
 0317                avatarReporterController.ReportAvatarRemoved();
 318            }
 319
 0320            avatarReporterController.SetUp(entity.scene.sceneData.sceneNumber, player.id);
 321
 0322            float height = AvatarSystemUtils.AVATAR_Y_OFFSET + avatar.extents.y;
 323
 0324            anchorPoints.Prepare(avatarContainer.transform, avatar.GetBones(), height);
 325
 0326            player.playerName.SetIsTalking(model.talking);
 0327            player.playerName.SetYOffset(Mathf.Max(MINIMUM_PLAYERNAME_HEIGHT, height));
 328
 0329            if (isNameDirty)
 330            {
 0331                UserProfile profile = userProfileBridge.Get(model.id);
 0332                player.playerName.SetName(model.name, profile?.hasClaimedName ?? false, profile?.isGuest ?? false);
 333            }
 0334        }
 335
 336        private void Update()
 337        {
 1338            if (player != null)
 339            {
 0340                player.worldPosition = entity.gameObject.transform.position;
 0341                player.forwardDirection = entity.gameObject.transform.forward;
 0342                avatarReporterController.ReportAvatarPosition(player.worldPosition);
 343            }
 1344        }
 345
 346        private void OnEntityTransformChanged(object newModel)
 347        {
 0348            DCLTransform.Model newTransformModel = (DCLTransform.Model)newModel;
 0349            OnEntityTransformChanged(newTransformModel.position, newTransformModel.rotation, !initializedPosition);
 0350        }
 351
 352        private void OnEntityTransformChanged(in Vector3 position, in Quaternion rotation, bool inmediate)
 353        {
 0354            if (isGlobalSceneAvatar)
 355            {
 0356                avatarMovementController.OnTransformChanged(position, rotation, inmediate);
 357            }
 358            else
 359            {
 0360                var scenePosition = Utils.GridToWorldPosition(entity.scene.sceneData.basePosition.x, entity.scene.sceneD
 0361                avatarMovementController.OnTransformChanged(scenePosition + position, rotation, inmediate);
 362            }
 0363            initializedPosition = true;
 0364        }
 365
 366        public override void OnPoolGet()
 367        {
 5368            base.OnPoolGet();
 369
 5370            everythingIsLoaded = false;
 5371            initializedPosition = false;
 5372            model = new AvatarModel();
 5373            player = null;
 5374        }
 375
 376        public void ApplyHideAvatarModifier()
 377        {
 0378            if (!currentActiveModifiers.ContainsKey(AvatarModifierAreaID.HIDE_AVATAR))
 379            {
 0380                avatar.AddVisibilityConstraint(VISIBILITY_CONSTRAINT_HIDE_AREA);
 0381                onPointerDown.gameObject.SetActive(false);
 0382                playerNameContainer.SetActive(false);
 0383                stickersControllers.ToggleHideArea(true);
 384            }
 0385            currentActiveModifiers.AddRefCount(AvatarModifierAreaID.HIDE_AVATAR);
 0386        }
 387
 388        public void RemoveHideAvatarModifier()
 389        {
 0390            currentActiveModifiers.RemoveRefCount(AvatarModifierAreaID.HIDE_AVATAR);
 0391            if (!currentActiveModifiers.ContainsKey(AvatarModifierAreaID.HIDE_AVATAR))
 392            {
 0393                avatar.RemoveVisibilityConstrain(VISIBILITY_CONSTRAINT_HIDE_AREA);
 0394                onPointerDown.gameObject.SetActive(true);
 0395                playerNameContainer.SetActive(true);
 0396                stickersControllers.ToggleHideArea(false);
 397            }
 0398        }
 399
 400        public void ApplyHidePassportModifier()
 401        {
 0402            if (!currentActiveModifiers.ContainsKey(AvatarModifierAreaID.DISABLE_PASSPORT))
 403            {
 0404                DisablePassport();
 405            }
 0406            currentActiveModifiers.AddRefCount(AvatarModifierAreaID.DISABLE_PASSPORT);
 0407        }
 408
 409        public void RemoveHidePassportModifier()
 410        {
 0411            currentActiveModifiers.RemoveRefCount(AvatarModifierAreaID.DISABLE_PASSPORT);
 0412            if (!currentActiveModifiers.ContainsKey(AvatarModifierAreaID.DISABLE_PASSPORT))
 413            {
 0414                EnablePasssport();
 415            }
 0416        }
 417
 418        private void EnablePasssport()
 419        {
 0420            if (onPointerDown.collider == null)
 0421                return;
 422
 0423            onPointerDown.SetPassportEnabled(true);
 0424        }
 425
 426        private void DisablePassport()
 427        {
 0428            if (onPointerDown.collider == null)
 0429                return;
 430
 0431            onPointerDown.SetPassportEnabled(false);
 0432        }
 433
 434        public override void Cleanup()
 435        {
 335436            base.Cleanup();
 437
 335438            playerName?.Hide(true);
 335439            if (player != null)
 440            {
 441                // AvatarShape used from the SDK doesn't register the avatars in 'otherPlayers'
 0442                if(!string.IsNullOrEmpty(player.id))
 0443                    otherPlayers.Remove(player.id);
 0444                player = null;
 445            }
 446
 335447            loadingCts?.Cancel();
 335448            loadingCts?.Dispose();
 335449            loadingCts = null;
 335450            currentLazyObserver?.RemoveListener(avatar.SetImpostorTexture);
 335451            avatar.Dispose();
 452
 335453            if (poolableObject != null)
 454            {
 15455                poolableObject.OnRelease -= Cleanup;
 456            }
 457
 335458            onPointerDown.OnPointerDownReport -= PlayerClicked;
 335459            onPointerDown.OnPointerEnterReport -= PlayerPointerEnter;
 335460            onPointerDown.OnPointerExitReport -= PlayerPointerExit;
 335461            outlineOnHover.OnPointerEnterReport -= PlayerPointerEnter;
 335462            outlineOnHover.OnPointerExitReport -= PlayerPointerExit;
 463
 335464            if (entity != null)
 465            {
 5466                entity.OnTransformChange = null;
 5467                entity = null;
 468            }
 469
 335470            avatarReporterController.ReportAvatarRemoved();
 335471        }
 472
 473        public void UpdateOutOfBoundariesState(bool isInsideBoundaries)
 474        {
 1475            if (scene.isPersistent)
 0476                isInsideBoundaries = true;
 477
 1478            if(isInsideBoundaries)
 0479                avatar.RemoveVisibilityConstrain(VISIBILITY_CONSTRAINT_OUTSIDE_SCENE_BOUNDS);
 480            else
 1481                avatar.AddVisibilityConstraint(VISIBILITY_CONSTRAINT_OUTSIDE_SCENE_BOUNDS);
 482
 1483            onPointerDown.gameObject.SetActive(isInsideBoundaries);
 1484            playerNameContainer.SetActive(isInsideBoundaries);
 1485            stickersControllers.ToggleHideArea(!isInsideBoundaries);
 1486        }
 487
 1488        public override int GetClassId() { return (int) CLASS_ID_COMPONENT.AVATAR_SHAPE; }
 489
 490        [ContextMenu("Print current profile")]
 0491        private void PrintCurrentProfile() { Debug.Log(JsonUtility.ToJson(model)); }
 492
 493    }
 494}