< Summary

Class:DCL.AvatarShape
Assembly:AvatarShape
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Avatar/AvatarShape.cs
Covered lines:72
Uncovered lines:123
Coverable lines:195
Total lines:446
Line coverage:36.9% (72 of 195)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
AvatarShape()0%110100%
Awake()0%330100%
GetStandardAvatar()0%110100%
GetAvatarWithHologram()0%110100%
Start()0%330100%
PlayerClicked()0%6200%
OnDestroy()0%330100%
ApplyChanges()0%171.7518022%
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%3.043083.33%
RemoveHidePassportModifier()0%12300%
Cleanup()0%8.068090%
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.Emotes;
 11using DCL.Helpers;
 12using DCL.Models;
 13using GPUSkinning;
 14using UnityEngine;
 15using Avatar = AvatarSystem.Avatar;
 16using LOD = AvatarSystem.LOD;
 17
 18namespace DCL
 19{
 20    public class AvatarShape : BaseComponent, IHideAvatarAreaHandler, IHidePassportAreaHandler
 21    {
 22        private const string CURRENT_PLAYER_ID = "CurrentPlayerInfoCardId";
 23        private const float MINIMUM_PLAYERNAME_HEIGHT = 2.7f;
 24        private const float AVATAR_PASSPORT_TOGGLE_ALPHA_THRESHOLD = 0.9f;
 25        private const string IN_HIDE_AREA = "IN_HIDE_AREA";
 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 GameObject playerNameContainer;
 38        internal IPlayerName playerName;
 39        internal IAvatarReporterController avatarReporterController;
 40
 41        private StringVariable currentPlayerInfoCardId;
 42
 43        public bool everythingIsLoaded;
 44
 45        bool initializedPosition = false;
 46
 47        private Player player = null;
 048        private BaseDictionary<string, Player> otherPlayers => DataStore.i.player.otherPlayers;
 49
 40050        private IAvatarAnchorPoints anchorPoints = new AvatarAnchorPoints();
 51        internal IAvatar avatar;
 40052        private readonly AvatarModel currentAvatar = new AvatarModel { wearables = new List<string>() };
 53        private CancellationTokenSource loadingCts;
 54        private ILazyTextureObserver currentLazyObserver;
 40055        private bool isGlobalSceneAvatar = true;
 56        private BaseRefCounter<AvatarModifierAreaID> currentActiveModifiers;
 57
 458        public override string componentName => "avatarShape";
 59
 60        private void Awake()
 61        {
 39862            model = new AvatarModel();
 39863            currentPlayerInfoCardId = Resources.Load<StringVariable>(CURRENT_PLAYER_ID);
 64
 39865            if (DataStore.i.avatarConfig.useHologramAvatar.Get())
 266                avatar = GetAvatarWithHologram();
 67            else
 39668                avatar = GetStandardAvatar();
 69
 39870            if (avatarReporterController == null)
 71            {
 39872                avatarReporterController = new AvatarReporterController(Environment.i.world.state);
 73            }
 39874        }
 75
 76        private Avatar GetStandardAvatar()
 77        {
 39678            Visibility visibility = new Visibility();
 39679            LOD avatarLOD = new LOD(avatarContainer, visibility, avatarMovementController);
 39680            AvatarAnimatorLegacy animator = GetComponentInChildren<AvatarAnimatorLegacy>();
 39681            return new Avatar(
 82                new AvatarCurator(new WearableItemResolver(), Environment.i.serviceLocator.Get<IEmotesCatalogService>())
 83                new Loader(new WearableLoaderFactory(), avatarContainer, new AvatarMeshCombinerHelper()),
 84                animator,
 85                visibility,
 86                avatarLOD,
 87                new SimpleGPUSkinning(),
 88                new GPUSkinningThrottler(),
 89                new EmoteAnimationEquipper(animator, DataStore.i.emotes));
 90        }
 91
 92        private AvatarWithHologram GetAvatarWithHologram()
 93        {
 294            Visibility visibility = new Visibility();
 295            LOD avatarLOD = new LOD(avatarContainer, visibility, avatarMovementController);
 296            AvatarAnimatorLegacy animator = GetComponentInChildren<AvatarAnimatorLegacy>();
 297            BaseAvatar baseAvatar = new BaseAvatar(avatarRevealContainer, armatureContainer, avatarLOD);
 298            return new AvatarWithHologram(
 99                    baseAvatar,
 100                    new AvatarCurator(new WearableItemResolver(), Environment.i.serviceLocator.Get<IEmotesCatalogService
 101                    new Loader(new WearableLoaderFactory(), avatarContainer, new AvatarMeshCombinerHelper()),
 102                    animator,
 103                    visibility,
 104                    avatarLOD,
 105                    new SimpleGPUSkinning(),
 106                    new GPUSkinningThrottler(),
 107                    new EmoteAnimationEquipper(animator, DataStore.i.emotes));
 108        }
 109
 110        private void Start()
 111        {
 1112            playerName = GetComponentInChildren<IPlayerName>();
 1113            playerName?.Hide(true);
 1114            currentActiveModifiers ??= new BaseRefCounter<AvatarModifierAreaID>();
 1115        }
 116
 117        private void PlayerClicked()
 118        {
 0119            if (model == null)
 0120                return;
 0121            currentPlayerInfoCardId.Set(((AvatarModel) model).id);
 0122        }
 123
 124        public void OnDestroy()
 125        {
 398126            Cleanup();
 127
 398128            if (poolableObject != null && poolableObject.isInsidePool)
 5129                poolableObject.RemoveFromPool();
 398130        }
 131
 132        public override IEnumerator ApplyChanges(BaseModel newModel)
 133        {
 5134            isGlobalSceneAvatar = scene.sceneData.id == EnvironmentSettings.AVATAR_GLOBAL_SCENE_ID;
 5135            currentActiveModifiers ??= new BaseRefCounter<AvatarModifierAreaID>();
 136
 5137            ApplyHidePassportModifier();
 138
 5139            var model = (AvatarModel) newModel;
 140
 5141            bool needsLoading = !model.HaveSameWearablesAndColors(currentAvatar);
 5142            currentAvatar.CopyFrom(model);
 143
 5144            if (string.IsNullOrEmpty(model.bodyShape) || model.wearables.Count == 0)
 1145                yield break;
 146#if UNITY_EDITOR
 4147            gameObject.name = $"Avatar Shape {model.name}";
 148#endif
 4149            everythingIsLoaded = false;
 150
 151            bool avatarDone = false;
 152            bool avatarFailed = false;
 153
 4154            yield return null; //NOTE(Brian): just in case we have a Object.Destroy waiting to be resolved.
 155
 156            // To deal with the cases in which the entity transform was configured before the AvatarShape
 0157            if (!initializedPosition && scene.componentsManagerLegacy.HasComponent(entity, CLASS_ID_COMPONENT.TRANSFORM)
 158            {
 0159                initializedPosition = true;
 0160                OnEntityTransformChanged(entity.gameObject.transform.localPosition,
 161                    entity.gameObject.transform.localRotation, true);
 162            }
 163
 164            // NOTE: we subscribe here to transform changes since we might "lose" the message
 165            // if we subscribe after a any yield
 0166            entity.OnTransformChange -= OnEntityTransformChanged;
 0167            entity.OnTransformChange += OnEntityTransformChanged;
 168
 0169            var wearableItems = model.wearables.ToList();
 0170            wearableItems.Add(model.bodyShape);
 171
 0172            if (avatar.status != IAvatar.Status.Loaded || needsLoading)
 173            {
 0174                HashSet<string> emotes = new HashSet<string>(currentAvatar.emotes.Select(x => x.urn));
 0175                var embeddedEmotesSo = Resources.Load<EmbeddedEmotesSO>("EmbeddedEmotes");
 0176                if (DataStore.i.emotes.newFlowEnabled.Get())
 177                {
 0178                    emotes.UnionWith(embeddedEmotesSo.emotes.Select(x => x.id));
 179                }
 180                else
 181                {
 182                    //temporarily hardcoding the embedded emotes until the user profile provides the equipped ones
 183                    //TODO remove this when new flow is the default and we can los retrocompatibility
 184                    //temporarily hardcoding the embedded emotes until the user profile provides the equipped ones
 0185                    wearableItems.AddRange(embeddedEmotesSo.emotes.Select(x => x.id));
 186                }
 187
 188                //TODO Add Collider to the AvatarSystem
 189                //TODO Without this the collider could get triggered disabling the avatar container,
 190                // this would stop the loading process due to the underlying coroutines of the AssetLoader not starting
 0191                avatarCollider.gameObject.SetActive(false);
 192
 0193                SetImpostor(model.id);
 0194                loadingCts?.Cancel();
 0195                loadingCts?.Dispose();
 0196                loadingCts = new CancellationTokenSource();
 0197                if (DataStore.i.avatarConfig.useHologramAvatar.Get())
 198                {
 0199                    playerName.SetName(model.name);
 0200                    playerName.Show(true);
 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            RemoveHidePassportModifier();
 242
 0243            onPointerDown.SetColliderEnabled(isGlobalSceneAvatar);
 0244            onPointerDown.SetOnClickReportEnabled(isGlobalSceneAvatar);
 0245        }
 246
 247        public void SetImpostor(string userId)
 248        {
 0249            currentLazyObserver?.RemoveListener(avatar.SetImpostorTexture);
 0250            if (string.IsNullOrEmpty(userId))
 0251                return;
 252
 0253            UserProfile userProfile = UserProfileController.GetProfileByUserId(userId);
 0254            if (userProfile == null)
 0255                return;
 256
 0257            currentLazyObserver = userProfile.bodySnapshotObserver;
 0258            currentLazyObserver.AddListener(avatar.SetImpostorTexture);
 0259        }
 260
 0261        private void PlayerPointerExit() { playerName?.SetForceShow(false); }
 0262        private void PlayerPointerEnter() { playerName?.SetForceShow(true); }
 263
 264        private void UpdatePlayerStatus(AvatarModel model)
 265        {
 266            // Remove the player status if the userId changes
 0267            if (player != null && (player.id != model.id || player.name != model.name))
 0268                otherPlayers.Remove(player.id);
 269
 0270            if (isGlobalSceneAvatar && string.IsNullOrEmpty(model?.id))
 0271                return;
 272
 0273            bool isNew = player == null;
 0274            if (isNew)
 275            {
 0276                player = new Player();
 277            }
 278
 0279            bool isNameDirty = player.name != model.name;
 280
 0281            player.id = model.id;
 0282            player.name = model.name;
 0283            player.isTalking = model.talking;
 0284            player.worldPosition = entity.gameObject.transform.position;
 0285            player.avatar = avatar;
 0286            player.onPointerDownCollider = onPointerDown;
 0287            player.collider = avatarCollider;
 288
 0289            if (isNew)
 290            {
 0291                player.playerName = playerName;
 0292                player.playerName.Show();
 0293                player.anchorPoints = anchorPoints;
 0294                if (isGlobalSceneAvatar)
 295                {
 296                    // TODO: Note: This is having a problem, sometimes the users has been detected as new 2 times and it
 297                    // we should investigate this
 0298                    if (otherPlayers.ContainsKey(player.id))
 0299                        otherPlayers.Remove(player.id);
 0300                    otherPlayers.Add(player.id, player);
 301                }
 0302                avatarReporterController.ReportAvatarRemoved();
 303            }
 304
 0305            avatarReporterController.SetUp(entity.scene.sceneData.id, player.id);
 306
 0307            float height = AvatarSystemUtils.AVATAR_Y_OFFSET + avatar.extents.y;
 308
 0309            anchorPoints.Prepare(avatarContainer.transform, avatar.GetBones(), height);
 310
 0311            player.playerName.SetIsTalking(model.talking);
 0312            player.playerName.SetYOffset(Mathf.Max(MINIMUM_PLAYERNAME_HEIGHT, height));
 0313            if (isNameDirty)
 0314                player.playerName.SetName(model.name);
 0315        }
 316
 317        private void Update()
 318        {
 1319            if (player != null)
 320            {
 0321                player.worldPosition = entity.gameObject.transform.position;
 0322                player.forwardDirection = entity.gameObject.transform.forward;
 0323                avatarReporterController.ReportAvatarPosition(player.worldPosition);
 324            }
 1325        }
 326
 327        private void OnEntityTransformChanged(object newModel)
 328        {
 0329            DCLTransform.Model newTransformModel = (DCLTransform.Model)newModel;
 0330            OnEntityTransformChanged(newTransformModel.position, newTransformModel.rotation, !initializedPosition);
 0331        }
 332
 333        private void OnEntityTransformChanged(in Vector3 position, in Quaternion rotation, bool inmediate)
 334        {
 0335            if (isGlobalSceneAvatar)
 336            {
 0337                avatarMovementController.OnTransformChanged(position, rotation, inmediate);
 338            }
 339            else
 340            {
 0341                var scenePosition = Utils.GridToWorldPosition(entity.scene.sceneData.basePosition.x, entity.scene.sceneD
 0342                avatarMovementController.OnTransformChanged(scenePosition + position, rotation, inmediate);
 343            }
 0344            initializedPosition = true;
 0345        }
 346
 347        public override void OnPoolGet()
 348        {
 5349            base.OnPoolGet();
 350
 5351            everythingIsLoaded = false;
 5352            initializedPosition = false;
 5353            model = new AvatarModel();
 5354            player = null;
 5355        }
 356
 357        public void ApplyHideAvatarModifier()
 358        {
 0359            if (!currentActiveModifiers.ContainsKey(AvatarModifierAreaID.HIDE_AVATAR))
 360            {
 0361                avatar.AddVisibilityConstrain(IN_HIDE_AREA);
 0362                onPointerDown.gameObject.SetActive(false);
 0363                playerNameContainer.SetActive(false);
 0364                stickersControllers.ToggleHideArea(true);
 365            }
 0366            currentActiveModifiers.AddRefCount(AvatarModifierAreaID.HIDE_AVATAR);
 0367        }
 368
 369        public void RemoveHideAvatarModifier()
 370        {
 0371            currentActiveModifiers.RemoveRefCount(AvatarModifierAreaID.HIDE_AVATAR);
 0372            if (!currentActiveModifiers.ContainsKey(AvatarModifierAreaID.HIDE_AVATAR))
 373            {
 0374                avatar.RemoveVisibilityConstrain(IN_HIDE_AREA);
 0375                onPointerDown.gameObject.SetActive(true);
 0376                playerNameContainer.SetActive(true);
 0377                stickersControllers.ToggleHideArea(false);
 378            }
 0379        }
 380
 381        public void ApplyHidePassportModifier()
 382        {
 5383            if (!currentActiveModifiers.ContainsKey(AvatarModifierAreaID.DISABLE_PASSPORT))
 384            {
 5385                if (onPointerDown.collider == null)
 0386                    return;
 387
 5388                onPointerDown.SetPassportEnabled(false);
 389            }
 5390            currentActiveModifiers.AddRefCount(AvatarModifierAreaID.DISABLE_PASSPORT);
 5391        }
 392
 393        public void RemoveHidePassportModifier()
 394        {
 0395            currentActiveModifiers.RemoveRefCount(AvatarModifierAreaID.DISABLE_PASSPORT);
 0396            if (!currentActiveModifiers.ContainsKey(AvatarModifierAreaID.DISABLE_PASSPORT))
 397            {
 0398                if (onPointerDown.collider == null)
 0399                    return;
 400
 0401                onPointerDown.SetPassportEnabled(true);
 402            }
 0403        }
 404
 405        public override void Cleanup()
 406        {
 408407            base.Cleanup();
 408
 408409            playerName?.Hide(true);
 408410            if (player != null)
 411            {
 0412                otherPlayers.Remove(player.id);
 0413                player = null;
 414            }
 415
 408416            loadingCts?.Cancel();
 408417            loadingCts?.Dispose();
 408418            loadingCts = null;
 408419            currentLazyObserver?.RemoveListener(avatar.SetImpostorTexture);
 408420            avatar.Dispose();
 421
 408422            if (poolableObject != null)
 423            {
 15424                poolableObject.OnRelease -= Cleanup;
 425            }
 426
 408427            onPointerDown.OnPointerDownReport -= PlayerClicked;
 408428            onPointerDown.OnPointerEnterReport -= PlayerPointerEnter;
 408429            onPointerDown.OnPointerExitReport -= PlayerPointerExit;
 430
 408431            if (entity != null)
 432            {
 5433                entity.OnTransformChange = null;
 5434                entity = null;
 435            }
 436
 408437            avatarReporterController.ReportAvatarRemoved();
 408438        }
 439
 1440        public override int GetClassId() { return (int) CLASS_ID_COMPONENT.AVATAR_SHAPE; }
 441
 442        [ContextMenu("Print current profile")]
 0443        private void PrintCurrentProfile() { Debug.Log(JsonUtility.ToJson(model)); }
 444
 445    }
 446}