< Summary

Class:DCL.AvatarShape
Assembly:AvatarShape
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Avatar/AvatarShape.cs
Covered lines:80
Uncovered lines:135
Coverable lines:215
Total lines:481
Line coverage:37.2% (80 of 215)
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%169.2817019.23%
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%2.062075%
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
 40652        private IAvatarAnchorPoints anchorPoints = new AvatarAnchorPoints();
 53        internal IAvatar avatar;
 40654        private readonly AvatarModel currentAvatar = new AvatarModel { wearables = new List<string>() };
 55        private CancellationTokenSource loadingCts;
 56        private ILazyTextureObserver currentLazyObserver;
 40657        private bool isGlobalSceneAvatar = true;
 58        private BaseRefCounter<AvatarModifierAreaID> currentActiveModifiers;
 59
 460        public override string componentName => "avatarShape";
 61
 62        private void Awake()
 63        {
 40464            model = new AvatarModel();
 40465            currentPlayerInfoCardId = Resources.Load<StringVariable>(CURRENT_PLAYER_ID);
 66
 40467            if (DataStore.i.avatarConfig.useHologramAvatar.Get())
 268                avatar = GetAvatarWithHologram();
 69            else
 40270                avatar = GetStandardAvatar();
 71
 40472            if (avatarReporterController == null)
 73            {
 40474                avatarReporterController = new AvatarReporterController(Environment.i.world.state);
 75            }
 40476        }
 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 Avatar GetStandardAvatar()
 85        {
 40286            Visibility visibility = new Visibility();
 40287            LOD avatarLOD = new LOD(avatarContainer, visibility, avatarMovementController);
 40288            AvatarAnimatorLegacy animator = GetComponentInChildren<AvatarAnimatorLegacy>();
 40289            return new Avatar(
 90                new AvatarCurator(new WearableItemResolver(), Environment.i.serviceLocator.Get<IEmotesCatalogService>())
 91                new Loader(new WearableLoaderFactory(), avatarContainer, new AvatarMeshCombinerHelper()),
 92                animator,
 93                visibility,
 94                avatarLOD,
 95                new SimpleGPUSkinning(),
 96                new GPUSkinningThrottler(),
 97                new EmoteAnimationEquipper(animator, DataStore.i.emotes));
 98        }
 99
 100        private AvatarWithHologram GetAvatarWithHologram()
 101        {
 2102            Visibility visibility = new Visibility();
 2103            LOD avatarLOD = new LOD(avatarContainer, visibility, avatarMovementController);
 2104            AvatarAnimatorLegacy animator = GetComponentInChildren<AvatarAnimatorLegacy>();
 2105            BaseAvatar baseAvatar = new BaseAvatar(avatarRevealContainer, armatureContainer, avatarLOD);
 2106            return new AvatarWithHologram(
 107                    baseAvatar,
 108                    new AvatarCurator(new WearableItemResolver(), Environment.i.serviceLocator.Get<IEmotesCatalogService
 109                    new Loader(new WearableLoaderFactory(), avatarContainer, new AvatarMeshCombinerHelper()),
 110                    animator,
 111                    visibility,
 112                    avatarLOD,
 113                    new SimpleGPUSkinning(),
 114                    new GPUSkinningThrottler(),
 115                    new EmoteAnimationEquipper(animator, DataStore.i.emotes));
 116        }
 117
 118        private void Start()
 119        {
 1120            playerName = GetComponentInChildren<IPlayerName>();
 1121            playerName?.Hide(true);
 1122            currentActiveModifiers ??= new BaseRefCounter<AvatarModifierAreaID>();
 1123        }
 124
 125        private void PlayerClicked()
 126        {
 0127            if (model == null)
 0128                return;
 0129            currentPlayerInfoCardId.Set(((AvatarModel) model).id);
 0130        }
 131
 132        public void OnDestroy()
 133        {
 404134            if(entity != null)
 0135                DataStore.i.sceneBoundariesChecker?.Remove(entity,this);
 136
 404137            Cleanup();
 138
 404139            if (poolableObject != null && poolableObject.isInsidePool)
 5140                poolableObject.RemoveFromPool();
 404141        }
 142
 143        public override IEnumerator ApplyChanges(BaseModel newModel)
 144        {
 5145            isGlobalSceneAvatar = scene.sceneData.id == EnvironmentSettings.AVATAR_GLOBAL_SCENE_ID;
 146
 5147            DisablePassport();
 148
 5149            var model = (AvatarModel) newModel;
 150
 5151            bool needsLoading = !model.HaveSameWearablesAndColors(currentAvatar);
 5152            currentAvatar.CopyFrom(model);
 153
 5154            if (string.IsNullOrEmpty(model.bodyShape) || model.wearables.Count == 0)
 1155                yield break;
 156#if UNITY_EDITOR
 4157            gameObject.name = $"Avatar Shape {model.name}";
 158#endif
 4159            everythingIsLoaded = false;
 160
 161            bool avatarDone = false;
 162            bool avatarFailed = false;
 163
 4164            yield return null; //NOTE(Brian): just in case we have a Object.Destroy waiting to be resolved.
 165
 166            // To deal with the cases in which the entity transform was configured before the AvatarShape
 0167            if (!initializedPosition && scene.componentsManagerLegacy.HasComponent(entity, CLASS_ID_COMPONENT.TRANSFORM)
 168            {
 0169                initializedPosition = true;
 0170                OnEntityTransformChanged(entity.gameObject.transform.localPosition,
 171                    entity.gameObject.transform.localRotation, true);
 172            }
 173
 174            // NOTE: we subscribe here to transform changes since we might "lose" the message
 175            // if we subscribe after a any yield
 0176            entity.OnTransformChange -= OnEntityTransformChanged;
 0177            entity.OnTransformChange += OnEntityTransformChanged;
 178
 0179            var wearableItems = model.wearables.ToList();
 0180            wearableItems.Add(model.bodyShape);
 181
 0182            if (avatar.status != IAvatar.Status.Loaded || needsLoading)
 183            {
 0184                HashSet<string> emotes = new HashSet<string>(currentAvatar.emotes.Select(x => x.urn));
 0185                var embeddedEmotesSo = Resources.Load<EmbeddedEmotesSO>("EmbeddedEmotes");
 0186                var embeddedEmoteIds = embeddedEmotesSo.emotes.Select(x => x.id);
 187                //here we add emote ids to both new and old emote loading flow to merge the results later
 188                //because some users might have emotes as wearables and others only as emotes
 0189                foreach (var emoteId in embeddedEmoteIds)
 190                {
 0191                    emotes.Add(emoteId);
 0192                    wearableItems.Add(emoteId);
 193                }
 194
 195                //TODO Add Collider to the AvatarSystem
 196                //TODO Without this the collider could get triggered disabling the avatar container,
 197                // this would stop the loading process due to the underlying coroutines of the AssetLoader not starting
 0198                avatarCollider.gameObject.SetActive(false);
 199
 0200                SetImpostor(model.id);
 0201                loadingCts?.Cancel();
 0202                loadingCts?.Dispose();
 0203                loadingCts = new CancellationTokenSource();
 0204                if (DataStore.i.avatarConfig.useHologramAvatar.Get())
 205                {
 0206                    playerName.SetName(model.name);
 0207                    playerName.Show(true);
 208                }
 209
 0210                avatar.Load(wearableItems, emotes.ToList(), new AvatarSettings
 211                {
 212                    playerName = model.name,
 213                    bodyshapeId = model.bodyShape,
 214                    eyesColor = model.eyeColor,
 215                    skinColor = model.skinColor,
 216                    hairColor = model.hairColor,
 217                }, loadingCts.Token);
 218
 219                // Yielding a UniTask doesn't do anything, we manually wait until the avatar is ready
 0220                yield return new WaitUntil(() => avatar.status == IAvatar.Status.Loaded);
 221            }
 222
 0223            avatar.PlayEmote(model.expressionTriggerId, model.expressionTriggerTimestamp);
 224
 0225            onPointerDown.OnPointerDownReport -= PlayerClicked;
 0226            onPointerDown.OnPointerDownReport += PlayerClicked;
 0227            onPointerDown.OnPointerEnterReport -= PlayerPointerEnter;
 0228            onPointerDown.OnPointerEnterReport += PlayerPointerEnter;
 0229            onPointerDown.OnPointerExitReport -= PlayerPointerExit;
 0230            onPointerDown.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            avatarCollider.gameObject.SetActive(true);
 245
 0246            everythingIsLoaded = true;
 0247            OnAvatarShapeUpdated?.Invoke(entity, this);
 248
 0249            EnablePasssport();
 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
 0269        private void PlayerPointerExit() { playerName?.SetForceShow(false); }
 0270        private void PlayerPointerEnter() { playerName?.SetForceShow(true); }
 271
 272        private void UpdatePlayerStatus(AvatarModel model)
 273        {
 274            // Remove the player status if the userId changes
 0275            if (player != null && (player.id != model.id || player.name != model.name))
 0276                otherPlayers.Remove(player.id);
 277
 0278            if (isGlobalSceneAvatar && string.IsNullOrEmpty(model?.id))
 0279                return;
 280
 0281            bool isNew = player == null;
 0282            if (isNew)
 283            {
 0284                player = new Player();
 285            }
 286
 0287            bool isNameDirty = player.name != model.name;
 288
 0289            player.id = model.id;
 0290            player.name = model.name;
 0291            player.isTalking = model.talking;
 0292            player.worldPosition = entity.gameObject.transform.position;
 0293            player.avatar = avatar;
 0294            player.onPointerDownCollider = onPointerDown;
 0295            player.collider = avatarCollider;
 296
 0297            if (isNew)
 298            {
 0299                player.playerName = playerName;
 0300                player.playerName.Show();
 0301                player.anchorPoints = anchorPoints;
 0302                if (isGlobalSceneAvatar)
 303                {
 304                    // TODO: Note: This is having a problem, sometimes the users has been detected as new 2 times and it
 305                    // we should investigate this
 0306                    if (otherPlayers.ContainsKey(player.id))
 0307                        otherPlayers.Remove(player.id);
 0308                    otherPlayers.Add(player.id, player);
 309                }
 0310                avatarReporterController.ReportAvatarRemoved();
 311            }
 312
 0313            avatarReporterController.SetUp(entity.scene.sceneData.id, player.id);
 314
 0315            float height = AvatarSystemUtils.AVATAR_Y_OFFSET + avatar.extents.y;
 316
 0317            anchorPoints.Prepare(avatarContainer.transform, avatar.GetBones(), height);
 318
 0319            player.playerName.SetIsTalking(model.talking);
 0320            player.playerName.SetYOffset(Mathf.Max(MINIMUM_PLAYERNAME_HEIGHT, height));
 0321            if (isNameDirty)
 0322                player.playerName.SetName(model.name);
 0323        }
 324
 325        private void Update()
 326        {
 1327            if (player != null)
 328            {
 0329                player.worldPosition = entity.gameObject.transform.position;
 0330                player.forwardDirection = entity.gameObject.transform.forward;
 0331                avatarReporterController.ReportAvatarPosition(player.worldPosition);
 332            }
 1333        }
 334
 335        private void OnEntityTransformChanged(object newModel)
 336        {
 0337            DCLTransform.Model newTransformModel = (DCLTransform.Model)newModel;
 0338            OnEntityTransformChanged(newTransformModel.position, newTransformModel.rotation, !initializedPosition);
 0339        }
 340
 341        private void OnEntityTransformChanged(in Vector3 position, in Quaternion rotation, bool inmediate)
 342        {
 0343            if (isGlobalSceneAvatar)
 344            {
 0345                avatarMovementController.OnTransformChanged(position, rotation, inmediate);
 0346            }
 347            else
 348            {
 0349                var scenePosition = Utils.GridToWorldPosition(entity.scene.sceneData.basePosition.x, entity.scene.sceneD
 0350                avatarMovementController.OnTransformChanged(scenePosition + position, rotation, inmediate);
 351            }
 0352            initializedPosition = true;
 0353        }
 354
 355        public override void OnPoolGet()
 356        {
 5357            base.OnPoolGet();
 358
 5359            everythingIsLoaded = false;
 5360            initializedPosition = false;
 5361            model = new AvatarModel();
 5362            player = null;
 5363        }
 364
 365        public void ApplyHideAvatarModifier()
 366        {
 0367            if (!currentActiveModifiers.ContainsKey(AvatarModifierAreaID.HIDE_AVATAR))
 368            {
 0369                avatar.AddVisibilityConstraint(VISIBILITY_CONSTRAINT_HIDE_AREA);
 0370                onPointerDown.gameObject.SetActive(false);
 0371                playerNameContainer.SetActive(false);
 0372                stickersControllers.ToggleHideArea(true);
 373            }
 0374            currentActiveModifiers.AddRefCount(AvatarModifierAreaID.HIDE_AVATAR);
 0375        }
 376
 377        public void RemoveHideAvatarModifier()
 378        {
 0379            currentActiveModifiers.RemoveRefCount(AvatarModifierAreaID.HIDE_AVATAR);
 0380            if (!currentActiveModifiers.ContainsKey(AvatarModifierAreaID.HIDE_AVATAR))
 381            {
 0382                avatar.RemoveVisibilityConstrain(VISIBILITY_CONSTRAINT_HIDE_AREA);
 0383                onPointerDown.gameObject.SetActive(true);
 0384                playerNameContainer.SetActive(true);
 0385                stickersControllers.ToggleHideArea(false);
 386            }
 0387        }
 388
 389        public void ApplyHidePassportModifier()
 390        {
 0391            if (!currentActiveModifiers.ContainsKey(AvatarModifierAreaID.DISABLE_PASSPORT))
 392            {
 0393                DisablePassport();
 394            }
 0395            currentActiveModifiers.AddRefCount(AvatarModifierAreaID.DISABLE_PASSPORT);
 0396        }
 397
 398        public void RemoveHidePassportModifier()
 399        {
 0400            currentActiveModifiers.RemoveRefCount(AvatarModifierAreaID.DISABLE_PASSPORT);
 0401            if (!currentActiveModifiers.ContainsKey(AvatarModifierAreaID.DISABLE_PASSPORT))
 402            {
 0403                EnablePasssport();
 404            }
 0405        }
 406
 407        private void EnablePasssport()
 408        {
 0409            if (onPointerDown.collider == null)
 0410                return;
 411
 0412            onPointerDown.SetPassportEnabled(true);
 0413        }
 414
 415        private void DisablePassport()
 416        {
 5417            if (onPointerDown.collider == null)
 0418                return;
 419
 5420            onPointerDown.SetPassportEnabled(false);
 5421        }
 422
 423        public override void Cleanup()
 424        {
 414425            base.Cleanup();
 426
 414427            playerName?.Hide(true);
 414428            if (player != null)
 429            {
 430                // AvatarShape used from the SDK doesn't register the avatars in 'otherPlayers'
 0431                if(!string.IsNullOrEmpty(player.id))
 0432                    otherPlayers.Remove(player.id);
 0433                player = null;
 434            }
 435
 414436            loadingCts?.Cancel();
 414437            loadingCts?.Dispose();
 414438            loadingCts = null;
 414439            currentLazyObserver?.RemoveListener(avatar.SetImpostorTexture);
 414440            avatar.Dispose();
 441
 414442            if (poolableObject != null)
 443            {
 15444                poolableObject.OnRelease -= Cleanup;
 445            }
 446
 414447            onPointerDown.OnPointerDownReport -= PlayerClicked;
 414448            onPointerDown.OnPointerEnterReport -= PlayerPointerEnter;
 414449            onPointerDown.OnPointerExitReport -= PlayerPointerExit;
 450
 414451            if (entity != null)
 452            {
 5453                entity.OnTransformChange = null;
 5454                entity = null;
 455            }
 456
 414457            avatarReporterController.ReportAvatarRemoved();
 414458        }
 459
 460        public void UpdateOutOfBoundariesState(bool isInsideBoundaries)
 461        {
 1462            if (scene.isPersistent)
 0463                isInsideBoundaries = true;
 464
 1465            if(isInsideBoundaries)
 0466                avatar.RemoveVisibilityConstrain(VISIBILITY_CONSTRAINT_OUTSIDE_SCENE_BOUNDS);
 467            else
 1468                avatar.AddVisibilityConstraint(VISIBILITY_CONSTRAINT_OUTSIDE_SCENE_BOUNDS);
 469
 1470            onPointerDown.gameObject.SetActive(isInsideBoundaries);
 1471            playerNameContainer.SetActive(isInsideBoundaries);
 1472            stickersControllers.ToggleHideArea(!isInsideBoundaries);
 1473        }
 474
 1475        public override int GetClassId() { return (int) CLASS_ID_COMPONENT.AVATAR_SHAPE; }
 476
 477        [ContextMenu("Print current profile")]
 0478        private void PrintCurrentProfile() { Debug.Log(JsonUtility.ToJson(model)); }
 479
 480    }
 481}