< Summary

Class:DCL.AvatarShape
Assembly:AvatarShape
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/Avatar/AvatarShape.cs
Covered lines:82
Uncovered lines:77
Coverable lines:159
Total lines:359
Line coverage:51.5% (82 of 159)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
AvatarShape()0%110100%
Awake()0%220100%
Start()0%220100%
PlayerClicked()0%6200%
OnDestroy()0%330100%
ApplyChanges()0%28.7514057.78%
SetImpostor(...)0%8.744033.33%
PlayerPointerExit()0%6200%
PlayerPointerEnter()0%6200%
UpdatePlayerStatus(...)0%1321100%
Update()0%2.862040%
DisablePassport()0%2.062075%
EnablePassport()0%6200%
OnEntityTransformChanged(...)0%2100%
OnEntityTransformChanged(...)0%2.092071.43%
OnPoolGet()0%110100%
Cleanup()0%8.068090%
GetClassId()0%2100%
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
 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
 26        public static event Action<IDCLEntity, AvatarShape> OnAvatarShapeUpdated;
 27
 28        public GameObject avatarContainer;
 29        public Collider avatarCollider;
 30        public AvatarMovementController avatarMovementController;
 31        [SerializeField] private GameObject onloadParticlePrefab;
 32
 33        [SerializeField] internal AvatarOnPointerDown onPointerDown;
 34        internal IPlayerName playerName;
 35        internal IAvatarReporterController avatarReporterController;
 36
 37        private StringVariable currentPlayerInfoCardId;
 38
 39        public bool everythingIsLoaded;
 40
 41        bool initializedPosition = false;
 42
 43        private Player player = null;
 044        private BaseDictionary<string, Player> otherPlayers => DataStore.i.player.otherPlayers;
 45
 68746        private IAvatarAnchorPoints anchorPoints = new AvatarAnchorPoints();
 47        private IAvatar avatar;
 68748        private readonly AvatarModel currentAvatar = new AvatarModel { wearables = new List<string>() };
 49        private CancellationTokenSource loadingCts;
 50        private ILazyTextureObserver currentLazyObserver;
 68751        private bool isGlobalSceneAvatar = true;
 52
 53        private void Awake()
 54        {
 68655            model = new AvatarModel();
 68656            currentPlayerInfoCardId = Resources.Load<StringVariable>(CURRENT_PLAYER_ID);
 68657            Visibility visibility = new Visibility();
 68658            LOD avatarLOD = new LOD(avatarContainer, visibility, avatarMovementController);
 68659            AvatarAnimatorLegacy animator = GetComponentInChildren<AvatarAnimatorLegacy>();
 68660            avatar = new Avatar(
 61                new AvatarCurator(new WearableItemResolver()),
 62                new Loader(new WearableLoaderFactory(), avatarContainer, new AvatarMeshCombinerHelper()),
 63                animator,
 64                visibility,
 65                avatarLOD,
 66                new SimpleGPUSkinning(),
 67                new GPUSkinningThrottler(),
 68                new EmoteAnimationEquipper(animator, DataStore.i.emotes));
 69
 68670            if (avatarReporterController == null)
 71            {
 68672                avatarReporterController = new AvatarReporterController(Environment.i.world.state);
 73            }
 68674        }
 75
 76        private void Start()
 77        {
 178            playerName = GetComponentInChildren<IPlayerName>();
 179            playerName?.Hide(true);
 180        }
 81
 82        private void PlayerClicked()
 83        {
 084            if (model == null)
 085                return;
 086            currentPlayerInfoCardId.Set(((AvatarModel) model).id);
 087        }
 88
 89        public void OnDestroy()
 90        {
 68691            Cleanup();
 92
 68693            if (poolableObject != null && poolableObject.isInsidePool)
 294                poolableObject.RemoveFromPool();
 68695        }
 96
 97        public override IEnumerator ApplyChanges(BaseModel newModel)
 98        {
 299            isGlobalSceneAvatar = scene.sceneData.id == EnvironmentSettings.AVATAR_GLOBAL_SCENE_ID;
 100
 2101            DisablePassport();
 102
 2103            var model = (AvatarModel) newModel;
 104
 2105            bool needsLoading = !model.HaveSameWearablesAndColors(currentAvatar);
 2106            currentAvatar.CopyFrom(model);
 107
 2108            if (string.IsNullOrEmpty(model.bodyShape) || model.wearables.Count == 0)
 0109                yield break;
 110#if UNITY_EDITOR
 2111            gameObject.name = $"Avatar Shape {model.name}";
 112#endif
 2113            everythingIsLoaded = false;
 114
 115            bool avatarDone = false;
 116            bool avatarFailed = false;
 117
 2118            yield return null; //NOTE(Brian): just in case we have a Object.Destroy waiting to be resolved.
 119
 120            // To deal with the cases in which the entity transform was configured before the AvatarShape
 1121            if (!initializedPosition && entity.components.ContainsKey(DCL.Models.CLASS_ID_COMPONENT.TRANSFORM))
 122            {
 1123                initializedPosition = true;
 1124                OnEntityTransformChanged(entity.gameObject.transform.localPosition,
 125                    entity.gameObject.transform.localRotation, true);
 126            }
 127
 128            // NOTE: we subscribe here to transform changes since we might "lose" the message
 129            // if we subscribe after a any yield
 1130            entity.OnTransformChange -= OnEntityTransformChanged;
 1131            entity.OnTransformChange += OnEntityTransformChanged;
 132
 1133            var wearableItems = model.wearables.ToList();
 1134            wearableItems.Add(model.bodyShape);
 135
 136            //temporarily hardcoding the embedded emotes until the user profile provides the equipped ones
 1137            var embeddedEmotesSo = Resources.Load<EmbeddedEmotesSO>("EmbeddedEmotes");
 23138            wearableItems.AddRange(embeddedEmotesSo.emotes.Select(x => x.id));
 139
 1140            if (avatar.status != IAvatar.Status.Loaded || needsLoading)
 141            {
 142                //TODO Add Collider to the AvatarSystem
 143                //TODO Without this the collider could get triggered disabling the avatar container,
 144                // this would stop the loading process due to the underlying coroutines of the AssetLoader not starting
 1145                avatarCollider.gameObject.SetActive(false);
 146
 1147                SetImpostor(model.id);
 1148                loadingCts?.Cancel();
 1149                loadingCts?.Dispose();
 1150                loadingCts = new CancellationTokenSource();
 151
 1152                avatar.Load(wearableItems, new AvatarSettings
 153                {
 154                    playerName = model.name,
 155                    bodyshapeId = model.bodyShape,
 156                    eyesColor = model.eyeColor,
 157                    skinColor = model.skinColor,
 158                    hairColor = model.hairColor,
 159                }, loadingCts.Token);
 160
 161                // Yielding a UniTask doesn't do anything, we manually wait until the avatar is ready
 3162                yield return new WaitUntil(() => avatar.status == IAvatar.Status.Loaded);
 163
 0164                if (avatar.lodLevel <= 1)
 0165                    AvatarSystemUtils.SpawnAvatarLoadedParticles(avatarContainer.transform, onloadParticlePrefab);
 166            }
 167
 0168            avatar.PlayEmote(model.expressionTriggerId, model.expressionTriggerTimestamp);
 169
 0170            onPointerDown.OnPointerDownReport -= PlayerClicked;
 0171            onPointerDown.OnPointerDownReport += PlayerClicked;
 0172            onPointerDown.OnPointerEnterReport -= PlayerPointerEnter;
 0173            onPointerDown.OnPointerEnterReport += PlayerPointerEnter;
 0174            onPointerDown.OnPointerExitReport -= PlayerPointerExit;
 0175            onPointerDown.OnPointerExitReport += PlayerPointerExit;
 176
 0177            UpdatePlayerStatus(model);
 178
 0179            onPointerDown.Initialize(
 180                new OnPointerDown.Model()
 181                {
 182                    type = OnPointerDown.NAME,
 183                    button = WebInterface.ACTION_BUTTON.POINTER.ToString(),
 184                    hoverText = "view profile"
 185                },
 186                entity, player
 187            );
 188
 0189            avatarCollider.gameObject.SetActive(true);
 190
 0191            everythingIsLoaded = true;
 0192            OnAvatarShapeUpdated?.Invoke(entity, this);
 193
 0194            EnablePassport();
 195
 0196            onPointerDown.SetColliderEnabled(isGlobalSceneAvatar);
 0197            onPointerDown.SetOnClickReportEnabled(isGlobalSceneAvatar);
 0198        }
 199
 200        public void SetImpostor(string userId)
 201        {
 1202            currentLazyObserver?.RemoveListener(avatar.SetImpostorTexture);
 1203            if (string.IsNullOrEmpty(userId))
 1204                return;
 205
 0206            UserProfile userProfile = UserProfileController.GetProfileByUserId(userId);
 0207            if (userProfile == null)
 0208                return;
 209
 0210            currentLazyObserver = userProfile.bodySnapshotObserver;
 0211            currentLazyObserver.AddListener(avatar.SetImpostorTexture);
 0212        }
 213
 0214        private void PlayerPointerExit() { playerName?.SetForceShow(false); }
 0215        private void PlayerPointerEnter() { playerName?.SetForceShow(true); }
 216
 217        private void UpdatePlayerStatus(AvatarModel model)
 218        {
 219            // Remove the player status if the userId changes
 0220            if (player != null && (player.id != model.id || player.name != model.name))
 0221                otherPlayers.Remove(player.id);
 222
 0223            if (isGlobalSceneAvatar && string.IsNullOrEmpty(model?.id))
 0224                return;
 225
 0226            bool isNew = player == null;
 0227            if (isNew)
 228            {
 0229                player = new Player();
 230            }
 231
 0232            player.id = model.id;
 0233            player.name = model.name;
 0234            player.isTalking = model.talking;
 0235            player.worldPosition = entity.gameObject.transform.position;
 0236            player.avatar = avatar;
 0237            player.onPointerDownCollider = onPointerDown;
 0238            player.collider = avatarCollider;
 239
 0240            if (isNew)
 241            {
 0242                player.playerName = playerName;
 0243                player.playerName.SetName(player.name);
 0244                player.playerName.Show();
 0245                player.anchorPoints = anchorPoints;
 0246                if (isGlobalSceneAvatar)
 247                {
 0248                    otherPlayers.Add(player.id, player);
 249                }
 0250                avatarReporterController.ReportAvatarRemoved();
 251            }
 252
 0253            avatarReporterController.SetUp(entity.scene.sceneData.id, entity.entityId, player.id);
 254
 0255            float height = AvatarSystemUtils.AVATAR_Y_OFFSET + avatar.extents.y;
 256
 0257            anchorPoints.Prepare(avatarContainer.transform, avatar.GetBones(), height);
 258
 0259            player.playerName.SetIsTalking(model.talking);
 0260            player.playerName.SetYOffset(Mathf.Max(MINIMUM_PLAYERNAME_HEIGHT, height));
 0261        }
 262
 263        private void Update()
 264        {
 2265            if (player != null)
 266            {
 0267                player.worldPosition = entity.gameObject.transform.position;
 0268                player.forwardDirection = entity.gameObject.transform.forward;
 0269                avatarReporterController.ReportAvatarPosition(player.worldPosition);
 270            }
 2271        }
 272
 273        public void DisablePassport()
 274        {
 2275            if (onPointerDown.collider == null)
 0276                return;
 277
 2278            onPointerDown.SetPassportEnabled(false);
 2279        }
 280
 281        public void EnablePassport()
 282        {
 0283            if (onPointerDown.collider == null)
 0284                return;
 285
 0286            onPointerDown.SetPassportEnabled(true);
 0287        }
 288
 289        private void OnEntityTransformChanged(object newModel)
 290        {
 0291            DCLTransform.Model newTransformModel = (DCLTransform.Model)newModel;
 0292            OnEntityTransformChanged(newTransformModel.position, newTransformModel.rotation, !initializedPosition);
 0293        }
 294
 295        private void OnEntityTransformChanged(in Vector3 position, in Quaternion rotation, bool inmediate)
 296        {
 1297            if (isGlobalSceneAvatar)
 298            {
 0299                avatarMovementController.OnTransformChanged(position, rotation, inmediate);
 0300            }
 301            else
 302            {
 1303                var scenePosition = Utils.GridToWorldPosition(entity.scene.sceneData.basePosition.x, entity.scene.sceneD
 1304                avatarMovementController.OnTransformChanged(scenePosition + position, rotation, inmediate);
 305            }
 1306            initializedPosition = true;
 1307        }
 308
 309        public override void OnPoolGet()
 310        {
 2311            base.OnPoolGet();
 312
 2313            everythingIsLoaded = false;
 2314            initializedPosition = false;
 2315            model = new AvatarModel();
 2316            player = null;
 2317        }
 318
 319        public override void Cleanup()
 320        {
 689321            base.Cleanup();
 322
 689323            playerName?.Hide(true);
 689324            if (player != null)
 325            {
 0326                otherPlayers.Remove(player.id);
 0327                player = null;
 328            }
 329
 689330            loadingCts?.Cancel();
 689331            loadingCts?.Dispose();
 689332            loadingCts = null;
 689333            currentLazyObserver?.RemoveListener(avatar.SetImpostorTexture);
 689334            avatar.Dispose();
 335
 689336            if (poolableObject != null)
 337            {
 5338                poolableObject.OnRelease -= Cleanup;
 339            }
 340
 689341            onPointerDown.OnPointerDownReport -= PlayerClicked;
 689342            onPointerDown.OnPointerEnterReport -= PlayerPointerEnter;
 689343            onPointerDown.OnPointerExitReport -= PlayerPointerExit;
 344
 689345            if (entity != null)
 346            {
 2347                entity.OnTransformChange = null;
 2348                entity = null;
 349            }
 350
 689351            avatarReporterController.ReportAvatarRemoved();
 689352        }
 353
 0354        public override int GetClassId() { return (int) CLASS_ID_COMPONENT.AVATAR_SHAPE; }
 355
 356        [ContextMenu("Print current profile")]
 0357        private void PrintCurrentProfile() { Debug.Log(JsonUtility.ToJson(model)); }
 358    }
 359}