< Summary

Class:AvatarEditorHUDController
Assembly:AvatarEditorHUD
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/HUD/AvatarEditorHUD/Scripts/AvatarEditorHUDController.cs
Covered lines:327
Uncovered lines:166
Coverable lines:493
Total lines:988
Line coverage:66.3% (327 of 493)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
AvatarEditorHUDController()0%110100%
AvatarEditorHUDController(...)0%110100%
Initialize(...)0%110100%
SetCatalog(...)0%2.062075%
LoadUserProfile(...)0%110100%
LoadOwnedWereables(...)0%550100%
QueryNftCollections(...)0%7.464040%
RetryLoadOwnedWearables()0%2100%
PlayerRendererLoaded(...)0%6.136084.62%
LoadUserProfile(...)0%12.6112083.78%
EnsureWearablesCategoriesNotEmpty()0%8.028092.86%
WearableClicked(...)0%8.448080.95%
HairColorClicked(...)0%110100%
SkinColorClicked(...)0%110100%
EyesColorClicked(...)0%110100%
UpdateAvatarPreview(...)0%6.65060%
EquipHairColor(...)0%110100%
EquipEyesColor(...)0%110100%
EquipSkinColor(...)0%110100%
EquipBodyShape(...)0%6.046089.47%
EquipWearable(...)0%6.226081.82%
UnequipWearable(...)0%3.033085.71%
EquipEmote(...)0%12300%
UnequipEmote(...)0%12300%
UnequipAllWearables()0%330100%
ProcessCatalog(...)0%440100%
AddWearable(...)0%440100%
RemoveWearable(...)0%20400%
RandomizeWearables()0%66095%
GetWearablesReplacedBy(...)0%7.017093.33%
SetVisibility(...)0%110100%
OnAvatarEditorVisibleChanged(...)0%110100%
SetVisibility_Internal(...)0%17.2715078.38%
Dispose()0%110100%
CleanUp()0%220100%
SetConfiguration(...)0%2100%
SaveAvatar(...)0%2.022081.82%
GoToMarketplaceOrConnectWallet()0%6200%
SellCollectible(...)0%12300%
ToggleVisibility()0%2100%
ConfigureBackpackInFullscreenMenuChanged(...)0%110100%
ExploreV2IsOpenChanged(...)0%12300%
LoadCollections()0%64050%
LoadUserThirdPartyWearables()0%42600%
ToggleThirdPartyCollection(...)0%2.52050%
FetchAndShowThirdPartyCollection(...)0%110100%
RemoveThirdPartyCollection(...)0%12300%
ShouldShowHideOtherWearablesToast(...)0%12300%
IsTryingToReplaceSkin(...)0%110100%
ShouldShowReplaceOtherWearablesToast(...)0%56700%
HandleEmotesCostumizationSelection(...)0%6200%
OnNewEmoteAdded(...)0%6200%
OnPreviewEmote(...)0%2100%
OnEmoteEquipped(...)0%6200%
OnEmoteUnequipped(...)0%6200%
OnRedirectToEmoteSelling(...)0%2100%
SendNewEquippedWearablesAnalytics(...)0%550100%
SendEquipWearableAnalytic(...)0%110100%
CreateEmotesController()0%110100%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Controllers/HUD/AvatarEditorHUD/Scripts/AvatarEditorHUDController.cs

#LineLine coverage
 1using DCL;
 2using DCL.EmotesCustomization;
 3using DCL.Helpers;
 4using DCL.Interface;
 5using DCL.NotificationModel;
 6using System;
 7using System.Collections.Generic;
 8using System.Linq;
 9using UnityEngine;
 10using UnityEngine.Rendering;
 11using UnityEngine.Rendering.Universal;
 12using Categories = WearableLiterals.Categories;
 13using Environment = DCL.Environment;
 14using Random = UnityEngine.Random;
 15using Type = DCL.NotificationModel.Type;
 16
 17public class AvatarEditorHUDController : IHUD
 18{
 19    private const int LOADING_OWNED_WEARABLES_RETRIES = 3;
 20    private const string LOADING_OWNED_WEARABLES_ERROR_MESSAGE = "There was a problem loading your wearables";
 21    private const string URL_MARKET_PLACE = "https://market.decentraland.org/browse?section=wearables";
 22    private const string URL_GET_A_WALLET = "https://docs.decentraland.org/get-a-wallet";
 23    private const string URL_SELL_COLLECTIBLE_GENERIC = "https://market.decentraland.org/account";
 24    private const string URL_SELL_SPECIFIC_COLLECTIBLE = "https://market.decentraland.org/contracts/{collectionId}/token
 25    private const string THIRD_PARTY_COLLECTIONS_FEATURE_FLAG = "third_party_collections";
 26    internal const string EQUIP_WEARABLE_METRIC = "equip_wearable";
 127    protected static readonly string[] categoriesThatMustHaveSelection = { Categories.BODY_SHAPE, Categories.UPPER_BODY,
 128    protected static readonly string[] categoriesToRandomize = { Categories.HAIR, Categories.EYES, Categories.EYEBROWS, 
 29
 30    [NonSerialized]
 31    public bool bypassUpdateAvatarPreview = false;
 32
 33    internal UserProfile userProfile;
 34    internal readonly IAnalytics analytics;
 35    internal readonly INewUserExperienceAnalytics newUserExperienceAnalytics;
 36    private BaseDictionary<string, WearableItem> catalog;
 10637    bool renderingEnabled => CommonScriptableObjects.rendererState.Get();
 10338    bool isPlayerRendererLoaded => DataStore.i.common.isPlayerRendererLoaded.Get();
 18339    BaseVariable<bool> avatarEditorVisible => DataStore.i.HUDs.avatarEditorVisible;
 13940    BaseVariable<Transform> configureBackpackInFullscreenMenu => DataStore.i.exploreV2.configureBackpackInFullscreenMenu
 9141    BaseVariable<bool> exploreV2IsOpen => DataStore.i.exploreV2.isOpen;
 30942    DataStore_EmotesCustomization emotesCustomizationDataStore => DataStore.i.emotesCustomization;
 043    DataStore_FeatureFlag featureFlagsDataStore => DataStore.i.featureFlags;
 44
 45    private readonly DataStore_FeatureFlag featureFlags;
 46
 4847    private readonly Dictionary<string, List<WearableItem>> wearablesByCategory = new Dictionary<string, List<WearableIt
 4848    protected readonly AvatarEditorHUDModel model = new AvatarEditorHUDModel();
 49
 50    private ColorList skinColorList;
 51    private ColorList eyeColorList;
 52    private ColorList hairColorList;
 53    private bool prevMouseLockState = false;
 4854    private int ownedWearablesRemainingRequests = LOADING_OWNED_WEARABLES_RETRIES;
 55    private bool ownedWearablesAlreadyLoaded = false;
 4856    private List<Nft> ownedNftCollectionsL1 = new List<Nft>();
 4857    private List<Nft> ownedNftCollectionsL2 = new List<Nft>();
 58    private bool avatarIsDirty = false;
 59    private float lastTimeOwnedWearablesChecked = 0;
 60    internal bool collectionsAlreadyLoaded = false;
 4861    private float prevRenderScale = 1.0f;
 62    private bool isAvatarPreviewReady;
 4863    private List<string> thirdPartyWearablesLoaded = new List<string>();
 64
 65    internal IEmotesCustomizationComponentController emotesCustomizationComponentController;
 66
 9167    private bool isThirdPartyCollectionsEnabled => featureFlags.flags.Get().IsFeatureEnabled(THIRD_PARTY_COLLECTIONS_FEA
 68
 69    public AvatarEditorHUDView view;
 70
 71    public event Action OnOpen;
 72    public event Action OnClose;
 73
 4874    public AvatarEditorHUDController(DataStore_FeatureFlag featureFlags, IAnalytics analytics)
 75    {
 4876        this.featureFlags = featureFlags;
 4877        this.analytics = analytics;
 4878        this.newUserExperienceAnalytics = new NewUserExperienceAnalytics(analytics);
 4879    }
 80
 81    public void Initialize(UserProfile userProfile,
 82        BaseDictionary<string, WearableItem> catalog,
 83        bool bypassUpdateAvatarPreview = false)
 84    {
 4885        this.userProfile = userProfile;
 4886        this.bypassUpdateAvatarPreview = bypassUpdateAvatarPreview;
 87
 4888        view = AvatarEditorHUDView.Create(this);
 89
 4890        view.skinsFeatureContainer.SetActive(true);
 4891        avatarEditorVisible.OnChange += OnAvatarEditorVisibleChanged;
 4892        OnAvatarEditorVisibleChanged(avatarEditorVisible.Get(), false);
 93
 4894        configureBackpackInFullscreenMenu.OnChange += ConfigureBackpackInFullscreenMenuChanged;
 4895        ConfigureBackpackInFullscreenMenuChanged(configureBackpackInFullscreenMenu.Get(), null);
 96
 4897        exploreV2IsOpen.OnChange += ExploreV2IsOpenChanged;
 98
 4899        skinColorList = Resources.Load<ColorList>("SkinTone");
 48100        hairColorList = Resources.Load<ColorList>("HairColor");
 48101        eyeColorList = Resources.Load<ColorList>("EyeColor");
 48102        view.SetColors(skinColorList.colors, hairColorList.colors, eyeColorList.colors);
 103
 48104        SetCatalog(catalog);
 105
 48106        LoadUserProfile(userProfile, true);
 48107        this.userProfile.OnUpdate += LoadUserProfile;
 108
 48109        view.SetSectionActive(AvatarEditorHUDView.EMOTES_SECTION_INDEX, false);
 110
 48111        emotesCustomizationComponentController = CreateEmotesController();
 48112        IEmotesCustomizationComponentView emotesSectionView = emotesCustomizationComponentController.Initialize(
 113            DataStore.i.emotesCustomization,
 114            DataStore.i.emotes,
 115            DataStore.i.exploreV2,
 116            DataStore.i.HUDs,
 117            userProfile,
 118            catalog);
 48119        emotesSectionView.viewTransform.SetParent(view.emotesSection.transform, false);
 48120        view.SetSectionActive(AvatarEditorHUDView.EMOTES_SECTION_INDEX, true);
 121
 48122        emotesCustomizationDataStore.isEmotesCustomizationSelected.OnChange += HandleEmotesCostumizationSelection;
 48123        emotesCustomizationDataStore.currentLoadedEmotes.OnAdded += OnNewEmoteAdded;
 124
 48125        emotesCustomizationComponentController.onEmotePreviewed += OnPreviewEmote;
 48126        emotesCustomizationComponentController.onEmoteEquipped += OnEmoteEquipped;
 48127        emotesCustomizationComponentController.onEmoteUnequipped += OnEmoteUnequipped;
 48128        emotesCustomizationComponentController.onEmoteSell += OnRedirectToEmoteSelling;
 129
 48130        DataStore.i.HUDs.isAvatarEditorInitialized.Set(true);
 131
 48132        view.SetThirdPartyCollectionsVisibility(isThirdPartyCollectionsEnabled);
 48133    }
 134
 135    public void SetCatalog(BaseDictionary<string, WearableItem> catalog)
 136    {
 48137        if (this.catalog != null)
 138        {
 0139            this.catalog.OnAdded -= AddWearable;
 0140            this.catalog.OnRemoved -= RemoveWearable;
 141        }
 142
 48143        this.catalog = catalog;
 144
 48145        ProcessCatalog(this.catalog);
 48146        this.catalog.OnAdded += AddWearable;
 48147        this.catalog.OnRemoved += RemoveWearable;
 48148    }
 149
 150    private void LoadUserProfile(UserProfile userProfile)
 151    {
 43152        LoadUserProfile(userProfile, false);
 43153        QueryNftCollections(userProfile.userId);
 43154    }
 155
 156    private void LoadOwnedWereables(UserProfile userProfile)
 157    {
 158        // If there is more than 1 minute that we have checked the owned wearables, we try it again
 159        // This is done in order to retrieved the wearables after you has claimed them
 43160        if ((Time.realtimeSinceStartup < lastTimeOwnedWearablesChecked + 60 &&
 161             (ownedWearablesAlreadyLoaded ||
 162              ownedWearablesRemainingRequests <= 0)) ||
 163            string.IsNullOrEmpty(userProfile.userId))
 42164            return;
 165
 1166        view.ShowCollectiblesLoadingSpinner(true);
 1167        view.ShowCollectiblesLoadingRetry(false);
 1168        lastTimeOwnedWearablesChecked = Time.realtimeSinceStartup;
 169
 1170        CatalogController.RequestOwnedWearables(userProfile.userId)
 171                         .Then((ownedWearables) =>
 172                         {
 0173                             ownedWearablesAlreadyLoaded = true;
 0174                             this.userProfile.SetInventory(ownedWearables.Select(x => x.id).Concat(thirdPartyWearablesLo
 0175                             LoadUserProfile(userProfile, true);
 0176                             view.ShowCollectiblesLoadingSpinner(false);
 0177                             view.ShowSkinPopulatedList(ownedWearables.Any(item => item.IsSkin()));
 0178                         })
 179                         .Catch((error) =>
 180                         {
 0181                             ownedWearablesRemainingRequests--;
 0182                             if (ownedWearablesRemainingRequests > 0)
 183                             {
 0184                                 Debug.LogWarning("Retrying owned wereables loading...");
 0185                                 LoadOwnedWereables(userProfile);
 0186                             }
 187                             else
 188                             {
 0189                                 NotificationsController.i.ShowNotification(new Model
 190                                 {
 191                                     message = LOADING_OWNED_WEARABLES_ERROR_MESSAGE,
 192                                     type = Type.GENERIC,
 193                                     timer = 10f,
 194                                     destroyOnFinish = true
 195                                 });
 196
 0197                                 view.ShowCollectiblesLoadingSpinner(false);
 0198                                 view.ShowCollectiblesLoadingRetry(true);
 0199                                 Debug.LogError(error);
 200                             }
 0201                         });
 1202    }
 203
 204    private void QueryNftCollections(string userId)
 205    {
 43206        if (string.IsNullOrEmpty(userId))
 43207            return;
 208
 0209        Environment.i.platform.serviceProviders.theGraph.QueryNftCollections(userProfile.userId, NftCollectionsLayer.ETH
 0210           .Then((nfts) => ownedNftCollectionsL1 = nfts)
 0211           .Catch((error) => Debug.LogError(error));
 212
 0213        Environment.i.platform.serviceProviders.theGraph.QueryNftCollections(userProfile.userId, NftCollectionsLayer.MAT
 0214           .Then((nfts) => ownedNftCollectionsL2 = nfts)
 0215           .Catch((error) => Debug.LogError(error));
 0216    }
 217
 218    public void RetryLoadOwnedWearables()
 219    {
 0220        ownedWearablesRemainingRequests = LOADING_OWNED_WEARABLES_RETRIES;
 0221        LoadOwnedWereables(userProfile);
 0222    }
 223
 224    private void PlayerRendererLoaded(bool current, bool previous)
 225    {
 15226        if (!current)
 0227            return;
 228
 15229        if (!ownedWearablesAlreadyLoaded)
 230        {
 15231            List<string> equippedOwnedWearables = new List<string>();
 78232            for (int i = 0; i < userProfile.avatar.wearables.Count; i++)
 233            {
 24234                if (catalog.TryGetValue(userProfile.avatar.wearables[i], out WearableItem wearable) &&
 235                    !wearable.data.tags.Contains(WearableLiterals.Tags.BASE_WEARABLE))
 236                {
 0237                    equippedOwnedWearables.Add(userProfile.avatar.wearables[i]);
 238                }
 239            }
 240
 15241            userProfile.SetInventory(equippedOwnedWearables.ToArray());
 242        }
 243
 15244        LoadUserProfile(userProfile, true);
 15245        DataStore.i.common.isPlayerRendererLoaded.OnChange -= PlayerRendererLoaded;
 15246    }
 247
 248    public void LoadUserProfile(UserProfile userProfile, bool forceLoading)
 249    {
 106250        bool avatarEditorNotVisible = renderingEnabled && !view.isOpen;
 106251        bool isPlaying = !Application.isBatchMode;
 252
 106253        if (!forceLoading)
 254        {
 43255            if (isPlaying && avatarEditorNotVisible)
 0256                return;
 257        }
 258
 106259        if (userProfile == null)
 0260            return;
 261
 106262        if (userProfile.avatar == null || string.IsNullOrEmpty(userProfile.avatar.bodyShape))
 3263            return;
 264
 103265        view.InitializeNavigationEvents(!userProfile.hasConnectedWeb3);
 266
 103267        CatalogController.wearableCatalog.TryGetValue(userProfile.avatar.bodyShape, out var bodyShape);
 268
 103269        if (bodyShape == null)
 270        {
 0271            return;
 272        }
 273
 103274        view.SetIsWeb3(userProfile.hasConnectedWeb3);
 275
 103276        ProcessCatalog(this.catalog);
 103277        EquipBodyShape(bodyShape);
 103278        EquipSkinColor(userProfile.avatar.skinColor);
 103279        EquipHairColor(userProfile.avatar.hairColor);
 103280        EquipEyesColor(userProfile.avatar.eyeColor);
 281
 103282        model.wearables.Clear();
 103283        view.UnselectAllWearables();
 284
 103285        int wearablesCount = userProfile.avatar.wearables.Count;
 286
 103287        if (isPlayerRendererLoaded)
 288        {
 180289            for (var i = 0; i < wearablesCount; i++)
 290            {
 55291                CatalogController.wearableCatalog.TryGetValue(userProfile.avatar.wearables[i], out var wearable);
 55292                if (wearable == null)
 293                {
 0294                    Debug.LogError($"Couldn't find wearable with ID {userProfile.avatar.wearables[i]}");
 0295                    continue;
 296                }
 297
 55298                if (wearable.IsEmote())
 0299                    EquipEmote(wearable);
 300                else
 55301                    EquipWearable(wearable);
 302            }
 303        }
 304
 103305        EnsureWearablesCategoriesNotEmpty();
 306
 103307        UpdateAvatarPreview(true);
 103308        isAvatarPreviewReady = true;
 103309    }
 310
 311    private void EnsureWearablesCategoriesNotEmpty()
 312    {
 103313        var categoriesInUse = model.wearables
 55314            .Where(x => !x.IsEmote())
 55315            .Select(x => x.data.category).ToArray();
 316
 1648317        for (var i = 0; i < categoriesThatMustHaveSelection.Length; i++)
 318        {
 721319            var category = categoriesThatMustHaveSelection[i];
 721320            if (category != Categories.BODY_SHAPE && !(categoriesInUse.Contains(category)))
 321            {
 322                WearableItem wearable;
 576323                var defaultItemId = WearableLiterals.DefaultWearables.GetDefaultWearable(model.bodyShape.id, category);
 576324                if (defaultItemId != null)
 325                {
 576326                    CatalogController.wearableCatalog.TryGetValue(defaultItemId, out wearable);
 576327                }
 328                else
 329                {
 0330                    wearable = wearablesByCategory[category].FirstOrDefault(x => x.SupportsBodyShape(model.bodyShape.id)
 331                }
 332
 576333                if (wearable != null)
 334                {
 576335                    EquipWearable(wearable);
 336                }
 337            }
 338        }
 103339    }
 340
 341    public void WearableClicked(string wearableId)
 342    {
 20343        CatalogController.wearableCatalog.TryGetValue(wearableId, out var wearable);
 23344        if (wearable == null) return;
 345
 17346        if (wearable.data.category == Categories.BODY_SHAPE)
 347        {
 3348            if (wearable.id == model.bodyShape.id)
 0349                return;
 3350            EquipBodyShape(wearable);
 3351        }
 352        else
 353        {
 14354            if (model.wearables.Contains(wearable))
 355            {
 2356                if (!categoriesThatMustHaveSelection.Contains(wearable.data.category))
 357                {
 0358                    UnequipWearable(wearable);
 0359                }
 360                else
 361                {
 2362                    return;
 363                }
 364            }
 365            else
 366            {
 12367                if (IsTryingToReplaceSkin(wearable))
 0368                    UnequipWearable(model.GetWearable(Categories.SKIN));
 369
 12370                var sameCategoryEquipped = model.GetWearable(wearable.data.category);
 12371                if (sameCategoryEquipped != null)
 2372                    UnequipWearable(sameCategoryEquipped);
 373
 12374                EquipWearable(wearable);
 375            }
 376        }
 377
 15378        UpdateAvatarPreview(false);
 15379    }
 380
 381    public void HairColorClicked(Color color)
 382    {
 2383        EquipHairColor(color);
 2384        view.SelectHairColor(model.hairColor);
 2385        UpdateAvatarPreview(true);
 2386    }
 387
 388    public void SkinColorClicked(Color color)
 389    {
 2390        EquipSkinColor(color);
 2391        view.SelectSkinColor(model.skinColor);
 2392        UpdateAvatarPreview(true);
 2393    }
 394
 395    public void EyesColorClicked(Color color)
 396    {
 2397        EquipEyesColor(color);
 2398        view.SelectEyeColor(model.eyesColor);
 2399        UpdateAvatarPreview(true);
 2400    }
 401
 402    protected virtual void UpdateAvatarPreview(bool skipAudio)
 403    {
 125404        if (bypassUpdateAvatarPreview)
 0405            return;
 406
 125407        AvatarModel modelToUpdate = model.ToAvatarModel();
 408
 409        // We always keep the loaded emotes into the Avatar Preview
 250410        foreach (string emoteId in emotesCustomizationDataStore.currentLoadedEmotes.Get())
 411        {
 0412            if (!modelToUpdate.wearables.Contains(emoteId))
 0413                modelToUpdate.wearables.Add(emoteId);
 414        }
 415
 125416        view.UpdateAvatarPreview(modelToUpdate, skipAudio);
 125417    }
 418
 419    private void EquipHairColor(Color color)
 420    {
 106421        model.hairColor = color;
 106422        view.SelectHairColor(model.hairColor);
 106423    }
 424
 425    private void EquipEyesColor(Color color)
 426    {
 106427        model.eyesColor = color;
 106428        view.SelectEyeColor(model.eyesColor);
 106429    }
 430
 431    private void EquipSkinColor(Color color)
 432    {
 105433        model.skinColor = color;
 105434        view.SelectSkinColor(model.skinColor);
 105435    }
 436
 437    private void EquipBodyShape(WearableItem bodyShape)
 438    {
 106439        if (bodyShape.data.category != Categories.BODY_SHAPE)
 440        {
 0441            Debug.LogError($"Item ({bodyShape.id} is not a body shape");
 0442            return;
 443        }
 444
 106445        if (model.bodyShape == bodyShape)
 53446            return;
 447
 53448        model.bodyShape = bodyShape;
 53449        view.UpdateSelectedBody(bodyShape);
 450
 53451        int wearablesCount = model.wearables.Count;
 164452        for (var i = wearablesCount - 1; i >= 0; i--)
 453        {
 29454            UnequipWearable(model.wearables[i]);
 455        }
 456
 53457        var defaultWearables = WearableLiterals.DefaultWearables.GetDefaultWearables(bodyShape.id);
 860458        for (var i = 0; i < defaultWearables.Length; i++)
 459        {
 377460            if (catalog.TryGetValue(defaultWearables[i], out var wearable))
 377461                EquipWearable(wearable);
 462        }
 53463    }
 464
 465    private void EquipWearable(WearableItem wearable)
 466    {
 1027467        if (wearable.IsEmote())
 0468            return;
 469
 1027470        if (!wearablesByCategory.ContainsKey(wearable.data.category))
 0471            return;
 472
 1027473        if (wearablesByCategory[wearable.data.category].Contains(wearable) && wearable.SupportsBodyShape(model.bodyShape
 474        {
 1027475            var toReplace = GetWearablesReplacedBy(wearable);
 1027476            toReplace.ForEach(UnequipWearable);
 1027477            model.wearables.Add(wearable);
 1027478            view.EquipWearable(wearable);
 1027479            avatarIsDirty = true;
 480        }
 1027481    }
 482
 483    private void UnequipWearable(WearableItem wearable)
 484    {
 33485        if (wearable.IsEmote())
 0486            return;
 487
 33488        if (model.wearables.Contains(wearable))
 489        {
 33490            model.wearables.Remove(wearable);
 33491            view.UnequipWearable(wearable);
 33492            avatarIsDirty = true;
 493        }
 33494    }
 495
 496    private void EquipEmote(WearableItem emote)
 497    {
 0498        if (!emote.IsEmote())
 0499            return;
 500
 0501        if (!model.wearables.Contains(emote))
 502        {
 0503            model.wearables.Add(emote);
 0504            avatarIsDirty = true;
 505        }
 0506    }
 507
 508    private void UnequipEmote(WearableItem emote)
 509    {
 0510        if (!emote.IsEmote())
 0511            return;
 512
 0513        if (model.wearables.Contains(emote))
 514        {
 0515            model.wearables.Remove(emote);
 0516            avatarIsDirty = true;
 517        }
 0518    }
 519
 520    public void UnequipAllWearables()
 521    {
 1012522        foreach (var wearable in model.wearables)
 523        {
 431524            if (!wearable.IsEmote())
 431525                view.UnequipWearable(wearable);
 526        }
 527
 75528        model.wearables.Clear();
 75529    }
 530
 531    private void ProcessCatalog(BaseDictionary<string, WearableItem> catalog)
 532    {
 151533        wearablesByCategory.Clear();
 151534        view.RemoveAllWearables();
 151535        using (var iterator = catalog.Get().GetEnumerator())
 536        {
 5551537            while (iterator.MoveNext())
 538            {
 5400539                if (iterator.Current.Value.IsEmote())
 540                    continue;
 541
 5400542                AddWearable(iterator.Current.Key, iterator.Current.Value);
 543            }
 151544        }
 545
 151546        view.RefreshSelectorsSize();
 151547    }
 548
 549    private void AddWearable(string id, WearableItem wearable)
 550    {
 5411551        if (!wearable.data.tags.Contains(WearableLiterals.Tags.BASE_WEARABLE) && userProfile.GetItemAmount(id) == 0)
 1190552            return;
 553
 4221554        if (!wearablesByCategory.ContainsKey(wearable.data.category))
 1650555            wearablesByCategory.Add(wearable.data.category, new List<WearableItem>());
 556
 4221557        wearablesByCategory[wearable.data.category].Add(wearable);
 4221558        view.AddWearable(wearable, userProfile.GetItemAmount(id),
 559            ShouldShowHideOtherWearablesToast,
 560            ShouldShowReplaceOtherWearablesToast);
 4221561    }
 562
 563    private void RemoveWearable(string id, WearableItem wearable)
 564    {
 0565        if (wearablesByCategory.ContainsKey(wearable.data.category))
 566        {
 0567            if (wearablesByCategory[wearable.data.category].Remove(wearable))
 568            {
 0569                if (wearablesByCategory[wearable.data.category].Count == 0)
 570                {
 0571                    wearablesByCategory.Remove(wearable.data.category);
 572                }
 573            }
 574        }
 575
 0576        view.RemoveWearable(wearable);
 0577    }
 578
 579    public void RandomizeWearables()
 580    {
 1581        EquipHairColor(view.GetRandomColor());
 1582        EquipEyesColor(view.GetRandomColor());
 583
 7584        List<WearableItem> wearablesToRemove = model.wearables.Where(x => !x.IsEmote()).ToList();
 14585        foreach (var wearable in wearablesToRemove)
 586        {
 6587            model.wearables.Remove(wearable);
 588        }
 589
 1590        view.UnselectAllWearables();
 1591        using (var iterator = wearablesByCategory.GetEnumerator())
 592        {
 12593            while (iterator.MoveNext())
 594            {
 11595                string category = iterator.Current.Key;
 11596                if (!categoriesToRandomize.Contains(category))
 597                {
 598                    continue;
 599                }
 600
 29601                var supportedWearables = iterator.Current.Value.Where(x => x.SupportsBodyShape(model.bodyShape.id)).ToAr
 7602                if (supportedWearables.Length == 0)
 603                {
 0604                    Debug.LogError($"Couldn't get any wearable for category {category} and bodyshape {model.bodyShape.id
 605                }
 606
 7607                var wearable = supportedWearables[Random.Range(0, supportedWearables.Length - 1)];
 7608                EquipWearable(wearable);
 609            }
 1610        }
 611
 1612        UpdateAvatarPreview(false);
 1613    }
 614
 615    private List<WearableItem> GetWearablesReplacedBy(WearableItem wearableItem)
 616    {
 1027617        var wearablesToReplace = new List<WearableItem>();
 1027618        var categoriesToReplace = new HashSet<string>(wearableItem.GetReplacesList(model.bodyShape.id) ?? new string[0])
 619
 1027620        int wearableCount = model.wearables.Count;
 7820621        for (int i = 0; i < wearableCount; i++)
 622        {
 2883623            var wearable = model.wearables[i];
 2883624            if (wearable == null) continue;
 625
 2883626            if (categoriesToReplace.Contains(wearable.data.category))
 627            {
 2628                wearablesToReplace.Add(wearable);
 2629            }
 630            else
 631            {
 632                //For retrocompatibility's sake we check current wearables against new one (compatibility matrix is symm
 2881633                HashSet<string> replacesList = new HashSet<string>(wearable.GetReplacesList(model.bodyShape.id) ?? new s
 2881634                if (replacesList.Contains(wearableItem.data.category))
 635                {
 0636                    wearablesToReplace.Add(wearable);
 637                }
 638            }
 639        }
 640
 1027641        return wearablesToReplace;
 642    }
 643
 88644    public void SetVisibility(bool visible) { avatarEditorVisible.Set(visible); }
 645
 184646    private void OnAvatarEditorVisibleChanged(bool current, bool previous) { SetVisibility_Internal(current); }
 647
 648    private void SetVisibility_Internal(bool visible)
 649    {
 92650        if (!visible && view.isOpen)
 651        {
 1652            view.ResetPreviewEmote();
 653
 1654            if (DataStore.i.common.isSignUpFlow.Get())
 0655                DataStore.i.virtualAudioMixer.sceneSFXVolume.Set(1f);
 656
 1657            Environment.i.messaging.manager.paused = false;
 1658            DataStore.i.skyboxConfig.avatarMatProfile.Set(AvatarMaterialProfile.InWorld);
 1659            if (prevMouseLockState && DataStore.i.common.isSignUpFlow.Get())
 660            {
 0661                Utils.LockCursor();
 662            }
 663
 664            // NOTE(Brian): SSAO doesn't work correctly with the offseted avatar preview if the renderScale != 1.0
 1665            var asset = GraphicsSettings.renderPipelineAsset as UniversalRenderPipelineAsset;
 1666            asset.renderScale = prevRenderScale;
 667
 1668            if (DataStore.i.common.isSignUpFlow.Get())
 0669                CommonScriptableObjects.isFullscreenHUDOpen.Set(false);
 670
 1671            DataStore.i.common.isPlayerRendererLoaded.OnChange -= PlayerRendererLoaded;
 672
 1673            OnClose?.Invoke();
 0674        }
 91675        else if (visible && !view.isOpen)
 676        {
 43677            if (DataStore.i.common.isSignUpFlow.Get())
 678            {
 0679                DataStore.i.virtualAudioMixer.sceneSFXVolume.Set(0f);
 0680                view.sectionSelector.Hide(true);
 0681            }
 682            else
 683            {
 43684                view.sectionSelector.Show(true);
 685            }
 686
 43687            LoadOwnedWereables(userProfile);
 43688            LoadCollections();
 43689            Environment.i.messaging.manager.paused = DataStore.i.common.isSignUpFlow.Get();
 43690            DataStore.i.skyboxConfig.avatarMatProfile.Set(AvatarMaterialProfile.InEditor);
 691
 43692            prevMouseLockState = Utils.IsCursorLocked;
 693
 43694            if (DataStore.i.common.isSignUpFlow.Get() || !DataStore.i.exploreV2.isInitialized.Get())
 43695                Utils.UnlockCursor();
 696
 697            // NOTE(Brian): SSAO doesn't work correctly with the offseted avatar preview if the renderScale != 1.0
 43698            var asset = GraphicsSettings.renderPipelineAsset as UniversalRenderPipelineAsset;
 43699            prevRenderScale = asset.renderScale;
 43700            asset.renderScale = 1.0f;
 701
 43702            if (DataStore.i.common.isSignUpFlow.Get())
 0703                CommonScriptableObjects.isFullscreenHUDOpen.Set(true);
 704
 43705            DataStore.i.common.isPlayerRendererLoaded.OnChange += PlayerRendererLoaded;
 706
 43707            OnOpen?.Invoke();
 708        }
 709
 92710        view.SetVisibility(visible);
 92711    }
 712
 713    public void Dispose()
 714    {
 43715        avatarEditorVisible.OnChange -= OnAvatarEditorVisibleChanged;
 43716        configureBackpackInFullscreenMenu.OnChange -= ConfigureBackpackInFullscreenMenuChanged;
 43717        DataStore.i.common.isPlayerRendererLoaded.OnChange -= PlayerRendererLoaded;
 43718        exploreV2IsOpen.OnChange -= ExploreV2IsOpenChanged;
 43719        emotesCustomizationDataStore.isEmotesCustomizationSelected.OnChange -= HandleEmotesCostumizationSelection;
 43720        emotesCustomizationDataStore.currentLoadedEmotes.OnAdded -= OnNewEmoteAdded;
 721
 43722        emotesCustomizationComponentController.onEmotePreviewed -= OnPreviewEmote;
 43723        emotesCustomizationComponentController.onEmoteEquipped -= OnEmoteEquipped;
 43724        emotesCustomizationComponentController.onEmoteUnequipped -= OnEmoteUnequipped;
 43725        emotesCustomizationComponentController.onEmoteSell -= OnRedirectToEmoteSelling;
 726
 43727        CleanUp();
 43728    }
 729
 730    public void CleanUp()
 731    {
 48732        UnequipAllWearables();
 733
 48734        if (view != null)
 48735            view.CleanUp();
 736
 48737        this.userProfile.OnUpdate -= LoadUserProfile;
 48738        this.catalog.OnAdded -= AddWearable;
 48739        this.catalog.OnRemoved -= RemoveWearable;
 48740        DataStore.i.common.isPlayerRendererLoaded.OnChange -= PlayerRendererLoaded;
 48741    }
 742
 0743    public void SetConfiguration(HUDConfiguration configuration) { SetVisibility(configuration.active); }
 744
 745    public void SaveAvatar(Texture2D face256Snapshot, Texture2D bodySnapshot)
 746    {
 1747        var avatarModel = model.ToAvatarModel();
 748
 1749        SendNewEquippedWearablesAnalytics(userProfile.avatar.wearables, avatarModel.wearables);
 750
 1751        WebInterface.SendSaveAvatar(avatarModel, face256Snapshot, bodySnapshot, DataStore.i.common.isSignUpFlow.Get());
 1752        userProfile.OverrideAvatar(avatarModel, face256Snapshot);
 753
 1754        if (DataStore.i.common.isSignUpFlow.Get())
 755        {
 0756            DataStore.i.HUDs.signupVisible.Set(true);
 0757            newUserExperienceAnalytics.AvatarEditSuccessNux();
 758        }
 759
 1760        emotesCustomizationDataStore.equippedEmotes.Set(emotesCustomizationDataStore.unsavedEquippedEmotes.Get());
 1761        avatarIsDirty = false;
 1762        SetVisibility(false);
 1763    }
 764
 765    public void GoToMarketplaceOrConnectWallet()
 766    {
 0767        if (userProfile.hasConnectedWeb3)
 0768            WebInterface.OpenURL(URL_MARKET_PLACE);
 769        else
 0770            WebInterface.OpenURL(URL_GET_A_WALLET);
 0771    }
 772
 773    public void SellCollectible(string collectibleId)
 774    {
 0775        var ownedCollectible = ownedNftCollectionsL1.FirstOrDefault(nft => nft.urn == collectibleId);
 0776        if (ownedCollectible == null)
 0777            ownedCollectible = ownedNftCollectionsL2.FirstOrDefault(nft => nft.urn == collectibleId);
 778
 0779        if (ownedCollectible != null)
 0780            WebInterface.OpenURL(URL_SELL_SPECIFIC_COLLECTIBLE.Replace("{collectionId}", ownedCollectible.collectionId).
 781        else
 0782            WebInterface.OpenURL(URL_SELL_COLLECTIBLE_GENERIC);
 0783    }
 784
 0785    public void ToggleVisibility() { SetVisibility(!view.isOpen); }
 786
 96787    private void ConfigureBackpackInFullscreenMenuChanged(Transform currentParentTransform, Transform previousParentTran
 788
 789    private void ExploreV2IsOpenChanged(bool current, bool previous)
 790    {
 0791        if (!current && avatarIsDirty)
 792        {
 0793            LoadUserProfile(userProfile, true);
 794
 0795            emotesCustomizationComponentController.RestoreEmoteSlots();
 796
 0797            avatarIsDirty = false;
 798        }
 0799    }
 800
 801    private void LoadCollections()
 802    {
 43803        if (!isThirdPartyCollectionsEnabled || collectionsAlreadyLoaded)
 43804            return;
 805
 0806        WearablesFetchingHelper.GetThirdPartyCollections()
 807            .Then((collections) =>
 808            {
 0809                view.LoadCollectionsDropdown(collections);
 0810                collectionsAlreadyLoaded = true;
 0811                LoadUserThirdPartyWearables();
 0812            })
 0813            .Catch((error) => Debug.LogError(error));
 0814    }
 815
 816    private void LoadUserThirdPartyWearables()
 817    {
 0818        List<string> collectionIdsToLoad = new List<string>();
 0819        foreach (string wearableId in userProfile.avatar.wearables)
 820        {
 0821            CatalogController.wearableCatalog.TryGetValue(wearableId, out var wearable);
 822
 0823            if (wearable != null && wearable.IsFromThirdPartyCollection)
 824            {
 0825                if (!collectionIdsToLoad.Contains(wearable.ThirdPartyCollectionId))
 0826                    collectionIdsToLoad.Add(wearable.ThirdPartyCollectionId);
 827            }
 828        }
 829
 0830        foreach (string collectionId in collectionIdsToLoad)
 831        {
 0832            view.ToggleThirdPartyCollection(collectionId, true);
 833        }
 0834    }
 835
 836    public void ToggleThirdPartyCollection(bool isOn, string collectionId, string collectionName)
 837    {
 1838        if (isOn)
 1839            FetchAndShowThirdPartyCollection(collectionId, collectionName);
 840        else
 0841            RemoveThirdPartyCollection(collectionId);
 0842    }
 843
 844    private void FetchAndShowThirdPartyCollection(string collectionId, string collectionName)
 845    {
 1846        view.BlockCollectionsDropdown(true);
 1847        CatalogController.RequestThirdPartyWearablesByCollection(userProfile.userId, collectionId)
 848            .Then(wearables =>
 849            {
 0850                if (wearables.Count().Equals(0)) view.ShowNoItemOfWearableCollectionWarning();
 851
 0852                foreach (var wearable in wearables)
 853                {
 0854                    if (!userProfile.ContainsInInventory(wearable.id))
 855                    {
 0856                        userProfile.AddToInventory(wearable.id);
 857
 0858                        if (!thirdPartyWearablesLoaded.Contains(wearable.id))
 0859                            thirdPartyWearablesLoaded.Add(wearable.id);
 860                    }
 861                }
 862
 0863                view.BlockCollectionsDropdown(false);
 0864                LoadUserProfile(userProfile, true);
 0865            })
 866            .Catch((error) =>
 867            {
 0868                view.BlockCollectionsDropdown(false);
 0869                Debug.LogError(error);
 0870            });
 1871    }
 872
 873    private void RemoveThirdPartyCollection(string collectionId)
 874    {
 0875        var wearablesToRemove = CatalogController.i.Wearables.GetValues()
 0876            .Where(wearable => !userProfile.HasEquipped(wearable.id)
 877                               && wearable.ThirdPartyCollectionId == collectionId)
 0878            .Select(item => item.id)
 879            .ToList();
 0880        CatalogController.i.Remove(wearablesToRemove);
 881
 0882        foreach (string wearableId in wearablesToRemove)
 883        {
 0884            userProfile.RemoveFromInventory(wearableId);
 0885            thirdPartyWearablesLoaded.Remove(wearableId);
 886        }
 887
 0888        LoadUserProfile(userProfile, true);
 0889    }
 890
 891    private bool ShouldShowHideOtherWearablesToast(WearableItem wearable)
 892    {
 0893        var isWearingSkinAlready = model.wearables.Any(item => item.IsSkin());
 0894        return wearable.IsSkin() && !isWearingSkinAlready;
 895    }
 896
 897    private bool IsTryingToReplaceSkin(WearableItem wearable)
 898    {
 12899        return model.wearables.Any(skin =>
 900        {
 80901            return skin.IsSkin()
 902                   && skin.DoesHide(wearable.data.category, model.bodyShape.id);
 903        });
 904    }
 905
 906    private bool ShouldShowReplaceOtherWearablesToast(WearableItem wearable)
 907    {
 0908        if (IsTryingToReplaceSkin(wearable)) return true;
 0909        var toReplace = GetWearablesReplacedBy(wearable);
 0910        if (wearable == null || toReplace.Count == 0) return false;
 0911        if (model.wearables.Contains(wearable)) return false;
 912
 913        // NOTE: why just 1?
 0914        if (toReplace.Count == 1)
 915        {
 0916            var w = toReplace[0];
 0917            if (w.data.category == wearable.data.category)
 0918                return false;
 919        }
 0920        return true;
 921    }
 922
 923
 924    private void HandleEmotesCostumizationSelection(bool current, bool previous)
 925    {
 0926        if (!current)
 0927            return;
 928
 0929        view.sectionSelector.GetSection(AvatarEditorHUDView.EMOTES_SECTION_INDEX).SelectToggle();
 0930    }
 931
 932    private void OnNewEmoteAdded(string emoteId)
 933    {
 0934        if (!isAvatarPreviewReady)
 0935            return;
 936
 0937        UpdateAvatarPreview(true);
 0938    }
 939
 0940    private void OnPreviewEmote(string emoteId) { view.PlayPreviewEmote(emoteId); }
 941
 942    private void OnEmoteEquipped(string emoteId)
 943    {
 0944        catalog.TryGetValue(emoteId, out WearableItem equippedEmote);
 945
 0946        if (equippedEmote != null)
 0947            EquipEmote(equippedEmote);
 0948    }
 949
 950    private void OnEmoteUnequipped(string emoteId)
 951    {
 0952        catalog.TryGetValue(emoteId, out WearableItem unequippedEmote);
 953
 0954        if (unequippedEmote != null)
 0955            UnequipEmote(unequippedEmote);
 0956    }
 957
 0958    private void OnRedirectToEmoteSelling(string emoteId) { SellCollectible(emoteId); }
 959
 960    internal void SendNewEquippedWearablesAnalytics(List<string> oldWearables, List<string> newWearables)
 961    {
 28962        for (int i = 0; i < newWearables.Count; i++)
 963        {
 12964            if (oldWearables.Contains(newWearables[i]))
 965                continue;
 966
 9967            catalog.TryGetValue(newWearables[i], out WearableItem newEquippedEmote);
 9968            if (newEquippedEmote != null && !newEquippedEmote.IsEmote())
 9969                SendEquipWearableAnalytic(newEquippedEmote);
 970        }
 2971    }
 972
 973    private void SendEquipWearableAnalytic(WearableItem equippedWearable)
 974    {
 9975        Dictionary<string, string> data = new Dictionary<string, string>();
 9976        data.Add("name", equippedWearable.GetName());
 9977        data.Add("rarity", equippedWearable.rarity);
 9978        data.Add("category", equippedWearable.data.category);
 9979        data.Add("linked_wearable", equippedWearable.IsFromThirdPartyCollection.ToString());
 9980        data.Add("third_party_collection_id", equippedWearable.ThirdPartyCollectionId);
 9981        data.Add("is_in_l2", equippedWearable.IsInL2().ToString());
 9982        data.Add("smart_item", equippedWearable.IsSmart().ToString());
 983
 9984        analytics.SendAnalytic(EQUIP_WEARABLE_METRIC, data);
 9985    }
 986
 48987    internal virtual IEmotesCustomizationComponentController CreateEmotesController() => new EmotesCustomizationComponen
 988}

Methods/Properties

AvatarEditorHUDController()
renderingEnabled()
isPlayerRendererLoaded()
avatarEditorVisible()
configureBackpackInFullscreenMenu()
exploreV2IsOpen()
emotesCustomizationDataStore()
featureFlagsDataStore()
AvatarEditorHUDController(DCL.DataStore_FeatureFlag, IAnalytics)
isThirdPartyCollectionsEnabled()
Initialize(UserProfile, .BaseDictionary[String,WearableItem], System.Boolean)
SetCatalog(.BaseDictionary[String,WearableItem])
LoadUserProfile(UserProfile)
LoadOwnedWereables(UserProfile)
QueryNftCollections(System.String)
RetryLoadOwnedWearables()
PlayerRendererLoaded(System.Boolean, System.Boolean)
LoadUserProfile(UserProfile, System.Boolean)
EnsureWearablesCategoriesNotEmpty()
WearableClicked(System.String)
HairColorClicked(UnityEngine.Color)
SkinColorClicked(UnityEngine.Color)
EyesColorClicked(UnityEngine.Color)
UpdateAvatarPreview(System.Boolean)
EquipHairColor(UnityEngine.Color)
EquipEyesColor(UnityEngine.Color)
EquipSkinColor(UnityEngine.Color)
EquipBodyShape(WearableItem)
EquipWearable(WearableItem)
UnequipWearable(WearableItem)
EquipEmote(WearableItem)
UnequipEmote(WearableItem)
UnequipAllWearables()
ProcessCatalog(.BaseDictionary[String,WearableItem])
AddWearable(System.String, WearableItem)
RemoveWearable(System.String, WearableItem)
RandomizeWearables()
GetWearablesReplacedBy(WearableItem)
SetVisibility(System.Boolean)
OnAvatarEditorVisibleChanged(System.Boolean, System.Boolean)
SetVisibility_Internal(System.Boolean)
Dispose()
CleanUp()
SetConfiguration(HUDConfiguration)
SaveAvatar(UnityEngine.Texture2D, UnityEngine.Texture2D)
GoToMarketplaceOrConnectWallet()
SellCollectible(System.String)
ToggleVisibility()
ConfigureBackpackInFullscreenMenuChanged(UnityEngine.Transform, UnityEngine.Transform)
ExploreV2IsOpenChanged(System.Boolean, System.Boolean)
LoadCollections()
LoadUserThirdPartyWearables()
ToggleThirdPartyCollection(System.Boolean, System.String, System.String)
FetchAndShowThirdPartyCollection(System.String, System.String)
RemoveThirdPartyCollection(System.String)
ShouldShowHideOtherWearablesToast(WearableItem)
IsTryingToReplaceSkin(WearableItem)
ShouldShowReplaceOtherWearablesToast(WearableItem)
HandleEmotesCostumizationSelection(System.Boolean, System.Boolean)
OnNewEmoteAdded(System.String)
OnPreviewEmote(System.String)
OnEmoteEquipped(System.String)
OnEmoteUnequipped(System.String)
OnRedirectToEmoteSelling(System.String)
SendNewEquippedWearablesAnalytics(System.Collections.Generic.List[String], System.Collections.Generic.List[String])
SendEquipWearableAnalytic(WearableItem)
CreateEmotesController()