< 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:382
Uncovered lines:200
Coverable lines:582
Total lines:1162
Line coverage:65.6% (382 of 582)
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%
OnAdditionalWearableRemoved(...)0%2100%
OnAdditionalWearableAdded(...)0%110100%
LoadUserProfile(...)0%110100%
LoadOwnedWereables(...)0%10.754025%
LoadOwnedEmotes()0%7.057090%
LoadOwnedEmotesTask()0%77.7517040.54%
QueryNftCollections(...)0%7.464040%
ReloadOwnedWearablesOnRefocus()0%2100%
PlayerRendererLoaded(...)0%6.136084.62%
LoadUserProfile(...)0%13.6113084.62%
EnsureWearablesCategoriesNotEmpty()0%8.028092.86%
WearableClicked(...)0%8.348082.61%
HairColorClicked(...)0%110100%
SkinColorClicked(...)0%110100%
EyesColorClicked(...)0%110100%
UpdateAvatarPreview()0%4.024088.89%
EquipHairColor(...)0%110100%
EquipEyesColor(...)0%110100%
EquipSkinColor(...)0%110100%
EquipBodyShape(...)0%6.046090%
EquipWearable(...)0%6.296080%
UnequipWearable(...)0%3.033085.71%
EquipEmote(...)0%6200%
UnequipEmote(...)0%6200%
UnequipAllWearables()0%330100%
ProcessCatalog(...)0%660100%
AddWearable(...)0%440100%
RemoveWearable(...)0%20400%
RandomizeWearables()0%66095.24%
GetWearablesReplacedBy(...)0%7.017093.33%
SetVisibility(...)0%110100%
OnAvatarEditorVisibleChanged(...)0%110100%
SetVisibility_Internal(...)0%16.815080%
Dispose()0%330100%
CleanUp()0%220100%
SetConfiguration(...)0%2100%
SaveAvatar(...)0%4.054085%
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%
ShouldShowIncompatibleWearableToast(...)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%
IsWearableUpdateInCooldown()0%2100%
IsEmotesUpdateInCooldown()0%110100%
AreWearablesAlreadyLoaded()0%6200%
OnApplicationFocus()0%2100%

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 System.Threading;
 10using Cysharp.Threading.Tasks;
 11using DCL.Emotes;
 12using UnityEngine;
 13using UnityEngine.Rendering;
 14using UnityEngine.Rendering.Universal;
 15using Categories = WearableLiterals.Categories;
 16using Environment = DCL.Environment;
 17using Random = UnityEngine.Random;
 18using Type = DCL.NotificationModel.Type;
 19
 20public class AvatarEditorHUDController : IHUD
 21{
 22    private const int LOADING_OWNED_WEARABLES_RETRIES = 3;
 23    private const string LOADING_OWNED_WEARABLES_ERROR_MESSAGE = "There was a problem loading your wearables";
 24    private const string URL_MARKET_PLACE = "https://market.decentraland.org/browse?section=wearables";
 25    private const string URL_GET_A_WALLET = "https://docs.decentraland.org/get-a-wallet";
 26    private const string URL_SELL_COLLECTIBLE_GENERIC = "https://market.decentraland.org/account";
 27    private const string URL_SELL_SPECIFIC_COLLECTIBLE = "https://market.decentraland.org/contracts/{collectionId}/token
 28    private const string THIRD_PARTY_COLLECTIONS_FEATURE_FLAG = "third_party_collections";
 29    internal const string EQUIP_WEARABLE_METRIC = "equip_wearable";
 130    protected static readonly string[] categoriesThatMustHaveSelection = { Categories.BODY_SHAPE, Categories.UPPER_BODY,
 131    protected static readonly string[] categoriesToRandomize = { Categories.HAIR, Categories.EYES, Categories.EYEBROWS, 
 32
 33    [NonSerialized]
 34    public bool bypassUpdateAvatarPreview = false;
 35
 36    internal UserProfile userProfile;
 37    internal readonly IAnalytics analytics;
 38    internal readonly INewUserExperienceAnalytics newUserExperienceAnalytics;
 39    private BaseDictionary<string, WearableItem> catalog;
 10640    bool renderingEnabled => CommonScriptableObjects.rendererState.Get();
 10241    bool isPlayerRendererLoaded => DataStore.i.common.isPlayerRendererLoaded.Get();
 18342    BaseVariable<bool> avatarEditorVisible => DataStore.i.HUDs.avatarEditorVisible;
 13943    BaseVariable<Transform> configureBackpackInFullscreenMenu => DataStore.i.exploreV2.configureBackpackInFullscreenMenu
 9144    BaseVariable<bool> exploreV2IsOpen => DataStore.i.exploreV2.isOpen;
 36245    DataStore_EmotesCustomization emotesCustomizationDataStore => DataStore.i.emotesCustomization;
 046    DataStore_FeatureFlag featureFlagsDataStore => DataStore.i.featureFlags;
 47
 48    private readonly DataStore_FeatureFlag featureFlags;
 49
 4850    private readonly Dictionary<string, List<WearableItem>> wearablesByCategory = new Dictionary<string, List<WearableIt
 4851    protected readonly AvatarEditorHUDModel model = new AvatarEditorHUDModel();
 52
 53    private ColorList skinColorList;
 54    private ColorList eyeColorList;
 55    private ColorList hairColorList;
 56    private bool prevMouseLockState = false;
 4857    private int ownedWearablesRemainingRequests = LOADING_OWNED_WEARABLES_RETRIES;
 58    private const int ownedWearableEmotesRequestRetryTime = 60;
 59    private bool ownedWearablesAlreadyLoaded = false;
 4860    private List<Nft> ownedNftCollectionsL1 = new List<Nft>();
 4861    private List<Nft> ownedNftCollectionsL2 = new List<Nft>();
 62    internal bool avatarIsDirty = false;
 63    private float lastTimeOwnedWearablesChecked = 0;
 4864    private float lastTimeOwnedEmotesChecked = float.MinValue;
 65    internal bool collectionsAlreadyLoaded = false;
 4866    private float prevRenderScale = 1.0f;
 67    private bool isAvatarPreviewReady;
 4868    private List<string> thirdPartyWearablesLoaded = new List<string>();
 4869    private List<string> thirdPartyCollectionsActive = new List<string>();
 4870    private CancellationTokenSource loadEmotesCTS = new CancellationTokenSource();
 71
 72    internal IEmotesCustomizationComponentController emotesCustomizationComponentController;
 73
 9174    private bool isThirdPartyCollectionsEnabled => featureFlags.flags.Get().IsFeatureEnabled(THIRD_PARTY_COLLECTIONS_FEA
 75
 76    public AvatarEditorHUDView view;
 77
 78    private bool loadingWearables;
 79    private WearableItem[] emotesLoadedAsWearables;
 80    internal AvatarEditorHUDAnimationController avatarEditorHUDAnimationController;
 81    public event Action OnOpen;
 82    public event Action OnClose;
 83
 4884    public AvatarEditorHUDController(DataStore_FeatureFlag featureFlags, IAnalytics analytics)
 85    {
 4886        this.featureFlags = featureFlags;
 4887        this.analytics = analytics;
 4888        this.newUserExperienceAnalytics = new NewUserExperienceAnalytics(analytics);
 4889    }
 90
 91    public void Initialize(UserProfile userProfile,
 92        BaseDictionary<string, WearableItem> catalog,
 93        bool bypassUpdateAvatarPreview = false
 94        )
 95    {
 4896        this.userProfile = userProfile;
 4897        this.bypassUpdateAvatarPreview = bypassUpdateAvatarPreview;
 98
 4899        view = AvatarEditorHUDView.Create(this);
 100
 48101        view.skinsFeatureContainer.SetActive(true);
 48102        avatarEditorVisible.OnChange += OnAvatarEditorVisibleChanged;
 48103        OnAvatarEditorVisibleChanged(avatarEditorVisible.Get(), false);
 104
 48105        configureBackpackInFullscreenMenu.OnChange += ConfigureBackpackInFullscreenMenuChanged;
 48106        ConfigureBackpackInFullscreenMenuChanged(configureBackpackInFullscreenMenu.Get(), null);
 107
 48108        exploreV2IsOpen.OnChange += ExploreV2IsOpenChanged;
 109
 48110        skinColorList = Resources.Load<ColorList>("SkinTone");
 48111        hairColorList = Resources.Load<ColorList>("HairColor");
 48112        eyeColorList = Resources.Load<ColorList>("EyeColor");
 48113        view.SetColors(skinColorList.colors, hairColorList.colors, eyeColorList.colors);
 114
 48115        SetCatalog(catalog);
 116
 48117        this.userProfile.OnUpdate += LoadUserProfile;
 118
 48119        view.SetSectionActive(AvatarEditorHUDView.EMOTES_SECTION_INDEX, false);
 120
 48121        emotesCustomizationComponentController = CreateEmotesController();
 48122        IEmotesCustomizationComponentView emotesSectionView = emotesCustomizationComponentController.Initialize(
 123            DataStore.i.emotesCustomization,
 124            DataStore.i.emotes,
 125            DataStore.i.exploreV2,
 126            DataStore.i.HUDs);
 127        //Initialize with embedded emotes
 48128        emotesCustomizationComponentController.SetEmotes(EmbeddedEmotesSO.Provide().emotes.ToArray());
 48129        emotesSectionView.viewTransform.SetParent(view.emotesSection.transform, false);
 48130        view.SetSectionActive(AvatarEditorHUDView.EMOTES_SECTION_INDEX, true);
 131
 48132        emotesCustomizationDataStore.isEmotesCustomizationSelected.OnChange += HandleEmotesCostumizationSelection;
 48133        emotesCustomizationDataStore.currentLoadedEmotes.OnAdded += OnNewEmoteAdded;
 134
 48135        emotesCustomizationComponentController.onEmotePreviewed += OnPreviewEmote;
 48136        emotesCustomizationComponentController.onEmoteEquipped += OnEmoteEquipped;
 48137        emotesCustomizationComponentController.onEmoteUnequipped += OnEmoteUnequipped;
 48138        emotesCustomizationComponentController.onEmoteSell += OnRedirectToEmoteSelling;
 139
 48140        LoadUserProfile(userProfile, true);
 141
 48142        DataStore.i.HUDs.isAvatarEditorInitialized.Set(true);
 143
 48144        view.SetThirdPartyCollectionsVisibility(isThirdPartyCollectionsEnabled);
 145
 48146        this.avatarEditorHUDAnimationController = new AvatarEditorHUDAnimationController(view, view.characterPreviewCont
 147
 48148        Environment.i.serviceLocator.Get<IApplicationFocusService>().OnApplicationFocus += OnApplicationFocus;
 48149    }
 150
 151    public void SetCatalog(BaseDictionary<string, WearableItem> catalog)
 152    {
 48153        if (this.catalog != null)
 154        {
 0155            this.catalog.OnAdded -= OnAdditionalWearableAdded;
 0156            this.catalog.OnRemoved -= OnAdditionalWearableRemoved;
 157        }
 158
 48159        this.catalog = catalog;
 160
 48161        ProcessCatalog(this.catalog);
 48162        this.catalog.OnAdded += OnAdditionalWearableAdded;
 48163        this.catalog.OnRemoved += OnAdditionalWearableRemoved;
 48164    }
 165
 166    private void OnAdditionalWearableRemoved(string s, WearableItem item)
 167    {
 0168        RemoveWearable(s, item);
 0169        view.RefreshSelectorsSize();
 0170    }
 171
 172    private void OnAdditionalWearableAdded(string id, WearableItem item)
 173    {
 11174        AddWearable(id, item);
 11175        view.RefreshSelectorsSize();
 11176    }
 177
 178    private void LoadUserProfile(UserProfile userProfile)
 179    {
 43180        LoadUserProfile(userProfile, false);
 43181        QueryNftCollections(userProfile.userId);
 43182    }
 183
 184    private void LoadOwnedWereables(UserProfile userProfile)
 185    {
 186        //If there is no userID we dont fetch owned wearabales
 43187        if(string.IsNullOrEmpty(userProfile.userId))
 43188            return;
 189
 190        // If wearables are loaded, we are in wearable cooldown, we dont fetch again owned wearables
 0191        if (AreWearablesAlreadyLoaded() && IsWearableUpdateInCooldown())
 0192            return;
 193
 0194        lastTimeOwnedWearablesChecked = Time.realtimeSinceStartup;
 195
 0196        loadingWearables = true;
 0197        CatalogController.RequestOwnedWearables(userProfile.userId)
 198                         .Then((ownedWearables) =>
 199                         {
 0200                             ownedWearablesAlreadyLoaded = true;
 201                             //Prior profile V1 emotes must be retrieved along the wearables, onwards they will be reque
 0202                             this.userProfile.SetInventory(ownedWearables.Select(x => x.id).Concat(thirdPartyWearablesLo
 0203                             LoadUserProfile(userProfile, true);
 0204                             if (userProfile != null && userProfile.avatar != null)
 205                             {
 0206                                 emotesLoadedAsWearables = ownedWearables.Where(x => x.IsEmote()).ToArray();
 207                             }
 0208                             loadingWearables = false;
 0209                         })
 210                         .Catch((error) =>
 211                         {
 0212                             ownedWearablesRemainingRequests--;
 0213                             if (ownedWearablesRemainingRequests > 0)
 214                             {
 0215                                 Debug.LogWarning("Retrying owned wereables loading...");
 0216                                 LoadOwnedWereables(userProfile);
 0217                             }
 218                             else
 219                             {
 0220                                 NotificationsController.i.ShowNotification(new Model
 221                                 {
 222                                     message = LOADING_OWNED_WEARABLES_ERROR_MESSAGE,
 223                                     type = Type.GENERIC,
 224                                     timer = 10f,
 225                                     destroyOnFinish = true
 226                                 });
 227
 0228                                 Debug.LogError(error);
 0229                                 loadingWearables = false;
 230                             }
 0231                         });
 0232    }
 233
 234    private void LoadOwnedEmotes()
 235    {
 236        //Only check emotes once every 60 seconds
 43237        if (IsEmotesUpdateInCooldown())
 0238            return;
 239
 43240        lastTimeOwnedEmotesChecked = Time.realtimeSinceStartup;
 241        //TODO only request OwnedEmotes once every minute
 43242        loadEmotesCTS?.Cancel();
 43243        loadEmotesCTS?.Dispose();
 43244        loadEmotesCTS = null;
 245        // we only follow this flow with new profiles
 43246        if (userProfile?.avatar != null)
 247        {
 43248            loadEmotesCTS = new CancellationTokenSource();
 43249            LoadOwnedEmotesTask(loadEmotesCTS.Token);
 250        }
 43251    }
 252
 253    private async UniTaskVoid LoadOwnedEmotesTask(CancellationToken ct = default, int retries = LOADING_OWNED_WEARABLES_
 254    {
 43255        var emotesCatalog = Environment.i.serviceLocator.Get<IEmotesCatalogService>();
 256        try
 257        {
 43258            var embeddedEmotes = Resources.Load<EmbeddedEmotesSO>("EmbeddedEmotes").emotes;
 43259            var emotes = await emotesCatalog.RequestOwnedEmotesAsync(userProfile.userId, ct);
 43260            var emotesList = emotes == null ? embeddedEmotes.Cast<WearableItem>().ToList() : emotes.Concat(embeddedEmote
 43261            var emotesFilter = new HashSet<string>();
 2752262            foreach (var e in emotesList)
 1333263                emotesFilter.Add(e.id);
 264
 43265            if(loadingWearables)
 0266                await UniTask.WaitWhile(() => loadingWearables, cancellationToken: ct);
 267
 43268            if (emotesLoadedAsWearables != null)
 269            {
 0270                foreach (var emoteAsWearable in emotesLoadedAsWearables)
 271                {
 0272                    if (emotesFilter.Contains(emoteAsWearable.id))
 273                        continue;
 274
 0275                    emotesList.Add(emoteAsWearable);
 276                }
 277
 0278                emotesLoadedAsWearables = null;
 279            }
 280
 43281            emotesCustomizationDataStore.UnequipMissingEmotes(emotesList);
 43282            emotesCustomizationComponentController.SetEmotes(emotesList.ToArray());
 283
 43284        }
 0285        catch (Exception e)
 286        {
 0287            OperationCanceledException opCanceled = e as OperationCanceledException;
 288            // If the cancellation was requested upwards, dont retry
 0289            if (opCanceled != null && ct.IsCancellationRequested)
 0290                return;
 291
 0292            if (retries > 0)
 293            {
 0294                LoadOwnedEmotesTask(ct, retries - 1);
 0295            }
 296            else
 297            {
 0298                if (opCanceled == null) // Ignore operation canceled exceptions when logging
 0299                    Debug.LogWarning(e.ToString());
 300                const string ERROR = "There was a problem loading your emotes";
 0301                NotificationsController.i.ShowNotification(new Model
 302                {
 303                    message = ERROR,
 304                    type = Type.GENERIC,
 305                    timer = 10f,
 306                    destroyOnFinish = true
 307                });
 308            }
 0309        }
 43310    }
 311
 312    private void QueryNftCollections(string userId)
 313    {
 43314        if (string.IsNullOrEmpty(userId))
 43315            return;
 316
 0317        Environment.i.platform.serviceProviders.theGraph.QueryNftCollections(userProfile.userId, NftCollectionsLayer.ETH
 0318           .Then((nfts) => ownedNftCollectionsL1 = nfts)
 0319           .Catch((error) => Debug.LogError(error));
 320
 0321        Environment.i.platform.serviceProviders.theGraph.QueryNftCollections(userProfile.userId, NftCollectionsLayer.MAT
 0322           .Then((nfts) => ownedNftCollectionsL2 = nfts)
 0323           .Catch((error) => Debug.LogError(error));
 0324    }
 325
 326    public void ReloadOwnedWearablesOnRefocus()
 327    {
 0328        ownedWearablesRemainingRequests = LOADING_OWNED_WEARABLES_RETRIES;
 0329        LoadOwnedWereables(userProfile);
 0330        LoadOwnedEmotes();
 0331    }
 332
 333    private void PlayerRendererLoaded(bool current, bool previous)
 334    {
 15335        if (!current)
 0336            return;
 337
 15338        if (!ownedWearablesAlreadyLoaded)
 339        {
 15340            List<string> equippedOwnedWearables = new List<string>();
 78341            for (int i = 0; i < userProfile.avatar.wearables.Count; i++)
 342            {
 24343                if (catalog.TryGetValue(userProfile.avatar.wearables[i], out WearableItem wearable) &&
 344                    !wearable.data.tags.Contains(WearableLiterals.Tags.BASE_WEARABLE))
 345                {
 0346                    equippedOwnedWearables.Add(userProfile.avatar.wearables[i]);
 347                }
 348            }
 349
 15350            userProfile.SetInventory(equippedOwnedWearables.ToArray());
 351        }
 352
 15353        LoadUserProfile(userProfile, true);
 15354        DataStore.i.common.isPlayerRendererLoaded.OnChange -= PlayerRendererLoaded;
 15355    }
 356
 357    public void LoadUserProfile(UserProfile userProfile, bool forceLoading)
 358    {
 106359        bool avatarEditorNotVisible = renderingEnabled && !view.isOpen;
 106360        bool isPlaying = !Application.isBatchMode;
 361
 106362        if (!forceLoading)
 363        {
 43364            if (isPlaying && avatarEditorNotVisible)
 0365                return;
 366        }
 367
 106368        if (userProfile == null)
 0369            return;
 370
 106371        if (userProfile.avatar == null || string.IsNullOrEmpty(userProfile.avatar.bodyShape))
 3372            return;
 373
 103374        view.InitializeNavigationEvents(!userProfile.hasConnectedWeb3);
 375
 103376        CatalogController.wearableCatalog.TryGetValue(userProfile.avatar.bodyShape, out var bodyShape);
 377
 103378        if (bodyShape == null)
 379        {
 0380            return;
 381        }
 382
 103383        view.SetIsWeb3(userProfile.hasConnectedWeb3);
 384
 103385        ProcessCatalog(this.catalog);
 386
 103387        if (avatarIsDirty)
 1388            return;
 389
 102390        EquipBodyShape(bodyShape);
 102391        EquipSkinColor(userProfile.avatar.skinColor);
 102392        EquipHairColor(userProfile.avatar.hairColor);
 102393        EquipEyesColor(userProfile.avatar.eyeColor);
 394
 102395        model.wearables.Clear();
 102396        view.UnselectAllWearables();
 397
 102398        int wearablesCount = userProfile.avatar.wearables.Count;
 399
 102400        if (isPlayerRendererLoaded)
 401        {
 160402            for (var i = 0; i < wearablesCount; i++)
 403            {
 46404                CatalogController.wearableCatalog.TryGetValue(userProfile.avatar.wearables[i], out var wearable);
 46405                if (wearable == null)
 406                {
 0407                    Debug.LogError($"Couldn't find wearable with ID {userProfile.avatar.wearables[i]}");
 0408                    continue;
 409                }
 410
 46411                if (wearable.IsEmote())
 0412                    EquipEmote(wearable);
 413                else
 46414                    EquipWearable(wearable);
 415            }
 416        }
 417
 102418        EnsureWearablesCategoriesNotEmpty();
 419
 102420        UpdateAvatarPreview();
 102421        isAvatarPreviewReady = true;
 102422    }
 423
 424    private void EnsureWearablesCategoriesNotEmpty()
 425    {
 102426        var categoriesInUse = model.wearables
 46427            .Where(x => !x.IsEmote())
 46428            .Select(x => x.data.category).ToArray();
 429
 1632430        for (var i = 0; i < categoriesThatMustHaveSelection.Length; i++)
 431        {
 714432            var category = categoriesThatMustHaveSelection[i];
 714433            if (category != Categories.BODY_SHAPE && !(categoriesInUse.Contains(category)))
 434            {
 435                WearableItem wearable;
 576436                var defaultItemId = WearableLiterals.DefaultWearables.GetDefaultWearable(model.bodyShape.id, category);
 576437                if (defaultItemId != null)
 438                {
 576439                    CatalogController.wearableCatalog.TryGetValue(defaultItemId, out wearable);
 576440                }
 441                else
 442                {
 0443                    wearable = wearablesByCategory[category].FirstOrDefault(x => x.SupportsBodyShape(model.bodyShape.id)
 444                }
 445
 576446                if (wearable != null)
 447                {
 576448                    EquipWearable(wearable);
 449                }
 450            }
 451        }
 102452    }
 453
 454    public void WearableClicked(string wearableId)
 455    {
 20456        CatalogController.wearableCatalog.TryGetValue(wearableId, out var wearable);
 23457        if (wearable == null) return;
 458
 17459        if (wearable.data.category == Categories.BODY_SHAPE)
 460        {
 3461            if (wearable.id == model.bodyShape.id)
 0462                return;
 3463            EquipBodyShape(wearable);
 3464        }
 465        else
 466        {
 14467            if (model.wearables.Contains(wearable))
 468            {
 2469                if (!categoriesThatMustHaveSelection.Contains(wearable.data.category))
 470                {
 0471                    UnequipWearable(wearable);
 0472                }
 473                else
 474                {
 2475                    return;
 476                }
 477            }
 478            else
 479            {
 12480                if (IsTryingToReplaceSkin(wearable))
 0481                    UnequipWearable(model.GetWearable(Categories.SKIN));
 482
 12483                var sameCategoryEquipped = model.GetWearable(wearable.data.category);
 12484                if (sameCategoryEquipped != null)
 2485                    UnequipWearable(sameCategoryEquipped);
 486
 12487                EquipWearable(wearable);
 488            }
 489        }
 490
 15491        UpdateAvatarPreview();
 15492        view.AddFeedbackOnAppear();
 15493        avatarIsDirty = true;
 15494    }
 495
 496    public void HairColorClicked(Color color)
 497    {
 2498        EquipHairColor(color);
 2499        view.SelectHairColor(model.hairColor);
 2500        UpdateAvatarPreview();
 2501        view.AddFeedbackOnAppear();
 2502    }
 503
 504    public void SkinColorClicked(Color color)
 505    {
 2506        EquipSkinColor(color);
 2507        view.SelectSkinColor(model.skinColor);
 2508        UpdateAvatarPreview();
 2509    }
 510
 511    public void EyesColorClicked(Color color)
 512    {
 2513        EquipEyesColor(color);
 2514        view.SelectEyeColor(model.eyesColor);
 2515        UpdateAvatarPreview();
 2516    }
 517
 518    protected virtual void UpdateAvatarPreview()
 519    {
 124520        if (bypassUpdateAvatarPreview)
 0521            return;
 522
 124523        AvatarModel modelToUpdate = model.ToAvatarModel();
 524
 525        // We always keep the loaded emotes into the Avatar Preview
 7936526        foreach (string emoteId in emotesCustomizationDataStore.currentLoadedEmotes.Get())
 527        {
 3844528            modelToUpdate.emotes.Add(new AvatarModel.AvatarEmoteEntry() { urn = emoteId });
 529        }
 530
 124531        view.UpdateAvatarPreview(modelToUpdate);
 124532    }
 533
 534    private void EquipHairColor(Color color)
 535    {
 105536        model.hairColor = color;
 105537        view.SelectHairColor(model.hairColor);
 105538    }
 539
 540    private void EquipEyesColor(Color color)
 541    {
 105542        model.eyesColor = color;
 105543        view.SelectEyeColor(model.eyesColor);
 105544    }
 545
 546    private void EquipSkinColor(Color color)
 547    {
 104548        model.skinColor = color;
 104549        view.SelectSkinColor(model.skinColor);
 104550    }
 551
 552    private void EquipBodyShape(WearableItem bodyShape)
 553    {
 105554        if (bodyShape.data.category != Categories.BODY_SHAPE)
 555        {
 0556            Debug.LogError($"Item ({bodyShape.id} is not a body shape");
 0557            return;
 558        }
 559
 105560        if (model.bodyShape == bodyShape)
 43561            return;
 562
 62563        model.bodyShape = bodyShape;
 62564        emotesCustomizationComponentController.SetEquippedBodyShape(bodyShape.id);
 62565        view.UpdateSelectedBody(bodyShape);
 566
 62567        int wearablesCount = model.wearables.Count;
 182568        for (var i = wearablesCount - 1; i >= 0; i--)
 569        {
 29570            UnequipWearable(model.wearables[i]);
 571        }
 572
 62573        var defaultWearables = WearableLiterals.DefaultWearables.GetDefaultWearables(bodyShape.id);
 1022574        for (var i = 0; i < defaultWearables.Length; i++)
 575        {
 449576            if (catalog.TryGetValue(defaultWearables[i], out var wearable))
 449577                EquipWearable(wearable);
 578        }
 62579    }
 580
 581    private void EquipWearable(WearableItem wearable)
 582    {
 1090583        if (wearable.IsEmote())
 0584            return;
 585
 1090586        if (!wearablesByCategory.ContainsKey(wearable.data.category))
 0587            return;
 588
 1090589        if (wearablesByCategory[wearable.data.category].Contains(wearable) && wearable.SupportsBodyShape(model.bodyShape
 590        {
 1090591            var toReplace = GetWearablesReplacedBy(wearable);
 1090592            toReplace.ForEach(UnequipWearable);
 1090593            model.wearables.Add(wearable);
 1090594            view.EquipWearable(wearable);
 595        }
 1090596    }
 597
 598    private void UnequipWearable(WearableItem wearable)
 599    {
 33600        if (wearable.IsEmote())
 0601            return;
 602
 33603        if (model.wearables.Contains(wearable))
 604        {
 33605            model.wearables.Remove(wearable);
 33606            view.UnequipWearable(wearable);
 33607            avatarIsDirty = true;
 608        }
 33609    }
 610
 611    private void EquipEmote(WearableItem emote)
 612    {
 0613        if (!emote.IsEmote())
 0614            return;
 0615        avatarIsDirty = true;
 0616    }
 617
 618    private void UnequipEmote(WearableItem emote)
 619    {
 0620        if (!emote.IsEmote())
 0621            return;
 622
 0623        avatarIsDirty = true;
 0624    }
 625
 626    public void UnequipAllWearables()
 627    {
 1012628        foreach (var wearable in model.wearables)
 629        {
 431630            if (!wearable.IsEmote())
 431631                view.UnequipWearable(wearable);
 632        }
 633
 75634        model.wearables.Clear();
 75635    }
 636
 637    private void ProcessCatalog(BaseDictionary<string, WearableItem> catalog)
 638    {
 151639        wearablesByCategory.Clear();
 151640        view.RemoveAllWearables();
 151641        bool hasSkin = false;
 151642        bool hasCollectible = false;
 151643        using (var iterator = catalog.Get().GetEnumerator())
 644        {
 5551645            while (iterator.MoveNext())
 646            {
 5400647                if (iterator.Current.Value.IsEmote())
 648                    continue;
 649
 5400650                if (iterator.Current.Value.IsFromThirdPartyCollection
 651                    && !thirdPartyCollectionsActive.Contains(iterator.Current.Value.ThirdPartyCollectionId))
 652                    continue;
 653
 5400654                AddWearable(iterator.Current.Key, iterator.Current.Value);
 5400655                hasSkin = iterator.Current.Value.IsSkin() || hasSkin;
 5400656                hasCollectible = iterator.Current.Value.IsCollectible() || hasCollectible;
 657            }
 151658        }
 151659        view.ShowSkinPopulatedList(hasSkin);
 151660        view.ShowCollectiblesPopulatedList(hasCollectible);
 151661        view.RefreshSelectorsSize();
 151662    }
 663
 664    private void AddWearable(string id, WearableItem wearable)
 665    {
 5411666        if (!wearable.data.tags.Contains(WearableLiterals.Tags.BASE_WEARABLE) && userProfile.GetItemAmount(id) == 0)
 1190667            return;
 668
 4221669        if (!wearablesByCategory.ContainsKey(wearable.data.category))
 1650670            wearablesByCategory.Add(wearable.data.category, new List<WearableItem>());
 671
 4221672        wearablesByCategory[wearable.data.category].Add(wearable);
 4221673        view.AddWearable(wearable, userProfile.GetItemAmount(id),
 674            ShouldShowHideOtherWearablesToast,
 675            ShouldShowReplaceOtherWearablesToast,
 676            ShouldShowIncompatibleWearableToast);
 4221677    }
 678
 679    private void RemoveWearable(string id, WearableItem wearable)
 680    {
 0681        if (wearablesByCategory.ContainsKey(wearable.data.category))
 682        {
 0683            if (wearablesByCategory[wearable.data.category].Remove(wearable))
 684            {
 0685                if (wearablesByCategory[wearable.data.category].Count == 0)
 686                {
 0687                    wearablesByCategory.Remove(wearable.data.category);
 688                }
 689            }
 690        }
 691
 0692        view.RemoveWearable(wearable);
 0693    }
 694
 695    public void RandomizeWearables()
 696    {
 1697        EquipHairColor(view.GetRandomColor());
 1698        EquipEyesColor(view.GetRandomColor());
 699
 7700        List<WearableItem> wearablesToRemove = model.wearables.Where(x => !x.IsEmote()).ToList();
 14701        foreach (var wearable in wearablesToRemove)
 702        {
 6703            model.wearables.Remove(wearable);
 704        }
 705
 1706        view.UnselectAllWearables();
 1707        using (var iterator = wearablesByCategory.GetEnumerator())
 708        {
 12709            while (iterator.MoveNext())
 710            {
 11711                string category = iterator.Current.Key;
 11712                if (!categoriesToRandomize.Contains(category))
 713                {
 714                    continue;
 715                }
 716
 29717                var supportedWearables = iterator.Current.Value.Where(x => x.SupportsBodyShape(model.bodyShape.id)).ToAr
 7718                if (supportedWearables.Length == 0)
 719                {
 0720                    Debug.LogError($"Couldn't get any wearable for category {category} and bodyshape {model.bodyShape.id
 721                }
 722
 7723                var wearable = supportedWearables[Random.Range(0, supportedWearables.Length - 1)];
 7724                EquipWearable(wearable);
 725            }
 1726        }
 727
 1728        UpdateAvatarPreview();
 1729        view.AddFeedbackOnAppear();
 1730    }
 731
 732    private List<WearableItem> GetWearablesReplacedBy(WearableItem wearableItem)
 733    {
 1090734        var wearablesToReplace = new List<WearableItem>();
 1090735        var categoriesToReplace = new HashSet<string>(wearableItem.GetReplacesList(model.bodyShape.id) ?? new string[0])
 736
 1090737        int wearableCount = model.wearables.Count;
 8378738        for (int i = 0; i < wearableCount; i++)
 739        {
 3099740            var wearable = model.wearables[i];
 3099741            if (wearable == null) continue;
 742
 3099743            if (categoriesToReplace.Contains(wearable.data.category))
 744            {
 2745                wearablesToReplace.Add(wearable);
 2746            }
 747            else
 748            {
 749                //For retrocompatibility's sake we check current wearables against new one (compatibility matrix is symm
 3097750                HashSet<string> replacesList = new HashSet<string>(wearable.GetReplacesList(model.bodyShape.id) ?? new s
 3097751                if (replacesList.Contains(wearableItem.data.category))
 752                {
 0753                    wearablesToReplace.Add(wearable);
 754                }
 755            }
 756        }
 757
 1090758        return wearablesToReplace;
 759    }
 760
 88761    public void SetVisibility(bool visible) { avatarEditorVisible.Set(visible); }
 762
 184763    private void OnAvatarEditorVisibleChanged(bool current, bool previous) { SetVisibility_Internal(current); }
 764
 765    private void SetVisibility_Internal(bool visible)
 766    {
 92767        bool isSignUpFlow = DataStore.i.common.isSignUpFlow.Get();
 92768        if (!visible && view.isOpen)
 769        {
 1770            view.ResetPreviewEmote();
 771
 1772            if (isSignUpFlow)
 0773                DataStore.i.virtualAudioMixer.sceneSFXVolume.Set(1f);
 774
 1775            Environment.i.messaging.manager.paused = false;
 1776            DataStore.i.skyboxConfig.avatarMatProfile.Set(AvatarMaterialProfile.InWorld);
 1777            if (prevMouseLockState && isSignUpFlow)
 778            {
 0779                Utils.LockCursor();
 780            }
 781
 782            // NOTE(Brian): SSAO doesn't work correctly with the offseted avatar preview if the renderScale != 1.0
 1783            var asset = GraphicsSettings.renderPipelineAsset as UniversalRenderPipelineAsset;
 1784            asset.renderScale = prevRenderScale;
 785
 1786            if (isSignUpFlow)
 0787                CommonScriptableObjects.isFullscreenHUDOpen.Set(false);
 788
 1789            DataStore.i.common.isPlayerRendererLoaded.OnChange -= PlayerRendererLoaded;
 790
 1791            OnClose?.Invoke();
 0792        }
 91793        else if (visible && !view.isOpen)
 794        {
 43795            if (isSignUpFlow)
 796            {
 0797                DataStore.i.virtualAudioMixer.sceneSFXVolume.Set(0f);
 0798                view.sectionSelector.Hide(true);
 0799            }
 800            else
 801            {
 43802                view.sectionSelector.Show(true);
 803            }
 804
 43805            LoadOwnedWereables(userProfile);
 43806            if (!isSignUpFlow)
 43807                LoadOwnedEmotes();
 808
 43809            LoadCollections();
 43810            Environment.i.messaging.manager.paused = isSignUpFlow;
 43811            DataStore.i.skyboxConfig.avatarMatProfile.Set(AvatarMaterialProfile.InEditor);
 812
 43813            prevMouseLockState = Utils.IsCursorLocked;
 814
 43815            if (isSignUpFlow || !DataStore.i.exploreV2.isInitialized.Get())
 43816                Utils.UnlockCursor();
 817
 818            // NOTE(Brian): SSAO doesn't work correctly with the offseted avatar preview if the renderScale != 1.0
 43819            var asset = GraphicsSettings.renderPipelineAsset as UniversalRenderPipelineAsset;
 43820            prevRenderScale = asset.renderScale;
 43821            asset.renderScale = 1.0f;
 822
 43823            if (isSignUpFlow)
 0824                CommonScriptableObjects.isFullscreenHUDOpen.Set(true);
 825
 43826            DataStore.i.common.isPlayerRendererLoaded.OnChange += PlayerRendererLoaded;
 827
 43828            OnOpen?.Invoke();
 829        }
 830
 92831        view.SetVisibility(visible);
 92832    }
 833
 834    public void Dispose()
 835    {
 43836        loadEmotesCTS?.Cancel();
 43837        loadEmotesCTS?.Dispose();
 43838        loadEmotesCTS = null;
 839
 43840        avatarEditorVisible.OnChange -= OnAvatarEditorVisibleChanged;
 43841        configureBackpackInFullscreenMenu.OnChange -= ConfigureBackpackInFullscreenMenuChanged;
 43842        DataStore.i.common.isPlayerRendererLoaded.OnChange -= PlayerRendererLoaded;
 43843        exploreV2IsOpen.OnChange -= ExploreV2IsOpenChanged;
 43844        emotesCustomizationDataStore.isEmotesCustomizationSelected.OnChange -= HandleEmotesCostumizationSelection;
 43845        emotesCustomizationDataStore.currentLoadedEmotes.OnAdded -= OnNewEmoteAdded;
 846
 43847        emotesCustomizationComponentController.onEmotePreviewed -= OnPreviewEmote;
 43848        emotesCustomizationComponentController.onEmoteEquipped -= OnEmoteEquipped;
 43849        emotesCustomizationComponentController.onEmoteUnequipped -= OnEmoteUnequipped;
 43850        emotesCustomizationComponentController.onEmoteSell -= OnRedirectToEmoteSelling;
 851
 43852        avatarEditorHUDAnimationController.Dispose();
 43853        Environment.i.serviceLocator.Get<IApplicationFocusService>().OnApplicationFocus -= OnApplicationFocus;
 854
 43855        CleanUp();
 43856    }
 857
 858    public void CleanUp()
 859    {
 48860        UnequipAllWearables();
 861
 48862        if (view != null)
 48863            view.Dispose();
 864
 48865        this.userProfile.OnUpdate -= LoadUserProfile;
 48866        this.catalog.OnAdded -= AddWearable;
 48867        this.catalog.OnRemoved -= RemoveWearable;
 48868        DataStore.i.common.isPlayerRendererLoaded.OnChange -= PlayerRendererLoaded;
 48869    }
 870
 0871    public void SetConfiguration(HUDConfiguration configuration) { SetVisibility(configuration.active); }
 872
 873    public void SaveAvatar(Texture2D face256Snapshot, Texture2D bodySnapshot)
 874    {
 1875        var avatarModel = model.ToAvatarModel();
 876
 877        // Add the equipped emotes to the avatar model
 1878        List<AvatarModel.AvatarEmoteEntry> emoteEntries = new List<AvatarModel.AvatarEmoteEntry>();
 1879        int equippedEmotesCount = emotesCustomizationDataStore.unsavedEquippedEmotes.Count();
 22880        for (int i = 0; i < equippedEmotesCount; i++)
 881        {
 10882            var equippedEmote = emotesCustomizationDataStore.unsavedEquippedEmotes[i];
 10883            if (equippedEmote == null)
 884                continue;
 885
 0886            emoteEntries.Add(new AvatarModel.AvatarEmoteEntry { slot = i, urn = equippedEmote.id });
 887        }
 888
 1889        avatarModel.emotes = emoteEntries;
 890
 1891        SendNewEquippedWearablesAnalytics(userProfile.avatar.wearables, avatarModel.wearables);
 1892        emotesCustomizationDataStore.equippedEmotes.Set(emotesCustomizationDataStore.unsavedEquippedEmotes.Get());
 893
 1894        WebInterface.SendSaveAvatar(avatarModel, face256Snapshot, bodySnapshot, DataStore.i.common.isSignUpFlow.Get());
 1895        userProfile.OverrideAvatar(avatarModel, face256Snapshot);
 896
 1897        if (DataStore.i.common.isSignUpFlow.Get())
 898        {
 0899            DataStore.i.HUDs.signupVisible.Set(true);
 0900            newUserExperienceAnalytics.AvatarEditSuccessNux();
 901        }
 902
 1903        avatarIsDirty = false;
 1904        SetVisibility(false);
 1905    }
 906
 907    public void GoToMarketplaceOrConnectWallet()
 908    {
 0909        if (userProfile.hasConnectedWeb3)
 0910            WebInterface.OpenURL(URL_MARKET_PLACE);
 911        else
 0912            WebInterface.OpenURL(URL_GET_A_WALLET);
 0913    }
 914
 915    public void SellCollectible(string collectibleId)
 916    {
 0917        var ownedCollectible = ownedNftCollectionsL1.FirstOrDefault(nft => nft.urn == collectibleId);
 0918        if (ownedCollectible == null)
 0919            ownedCollectible = ownedNftCollectionsL2.FirstOrDefault(nft => nft.urn == collectibleId);
 920
 0921        if (ownedCollectible != null)
 0922            WebInterface.OpenURL(URL_SELL_SPECIFIC_COLLECTIBLE.Replace("{collectionId}", ownedCollectible.collectionId).
 923        else
 0924            WebInterface.OpenURL(URL_SELL_COLLECTIBLE_GENERIC);
 0925    }
 926
 0927    public void ToggleVisibility() { SetVisibility(!view.isOpen); }
 928
 96929    private void ConfigureBackpackInFullscreenMenuChanged(Transform currentParentTransform, Transform previousParentTran
 930
 931    private void ExploreV2IsOpenChanged(bool current, bool previous)
 932    {
 0933        if (!current && avatarIsDirty)
 934        {
 0935            avatarIsDirty = false;
 936
 0937            LoadUserProfile(userProfile, true);
 938
 0939            emotesCustomizationComponentController.RestoreEmoteSlots();
 940        }
 0941    }
 942
 943    private void LoadCollections()
 944    {
 43945        if (!isThirdPartyCollectionsEnabled || collectionsAlreadyLoaded)
 43946            return;
 947
 0948        WearablesFetchingHelper.GetThirdPartyCollections()
 949            .Then((collections) =>
 950            {
 0951                view.LoadCollectionsDropdown(collections);
 0952                collectionsAlreadyLoaded = true;
 0953                LoadUserThirdPartyWearables();
 0954            })
 0955            .Catch((error) => Debug.LogError(error));
 0956    }
 957
 958    private void LoadUserThirdPartyWearables()
 959    {
 0960        List<string> collectionIdsToLoad = new List<string>();
 0961        foreach (string wearableId in userProfile.avatar.wearables)
 962        {
 0963            CatalogController.wearableCatalog.TryGetValue(wearableId, out var wearable);
 964
 0965            if (wearable != null && wearable.IsFromThirdPartyCollection)
 966            {
 0967                if (!collectionIdsToLoad.Contains(wearable.ThirdPartyCollectionId))
 0968                    collectionIdsToLoad.Add(wearable.ThirdPartyCollectionId);
 969            }
 970        }
 971
 0972        foreach (string collectionId in collectionIdsToLoad)
 973        {
 0974            view.ToggleThirdPartyCollection(collectionId, true);
 975        }
 0976    }
 977
 978    public void ToggleThirdPartyCollection(bool isOn, string collectionId, string collectionName)
 979    {
 1980        if (isOn)
 1981            FetchAndShowThirdPartyCollection(collectionId, collectionName);
 982        else
 0983            RemoveThirdPartyCollection(collectionId);
 0984    }
 985
 986    private void FetchAndShowThirdPartyCollection(string collectionId, string collectionName)
 987    {
 1988        view.BlockCollectionsDropdown(true);
 1989        CatalogController.RequestThirdPartyWearablesByCollection(userProfile.userId, collectionId)
 990            .Then(wearables =>
 991            {
 0992                wearables = wearables.Where(x => x.ThirdPartyCollectionId == collectionId).ToArray();
 0993                if (wearables.Count().Equals(0)) view.ShowNoItemOfWearableCollectionWarning();
 0994                thirdPartyCollectionsActive.Add(collectionId);
 0995                foreach (var wearable in wearables)
 996                {
 0997                    if (!userProfile.ContainsInInventory(wearable.id))
 998                    {
 0999                        userProfile.AddToInventory(wearable.id);
 1000
 01001                        if (!thirdPartyWearablesLoaded.Contains(wearable.id))
 01002                            thirdPartyWearablesLoaded.Add(wearable.id);
 1003                    }
 1004                }
 1005
 01006                view.BlockCollectionsDropdown(false);
 01007                LoadUserProfile(userProfile, true);
 01008                view.RefreshSelectorsSize();
 01009            })
 1010            .Catch((error) =>
 1011            {
 01012                view.BlockCollectionsDropdown(false);
 01013                Debug.LogError(error);
 01014            });
 11015    }
 1016
 1017    private void RemoveThirdPartyCollection(string collectionId)
 1018    {
 01019        var wearablesToRemove = CatalogController.i.Wearables.GetValues()
 01020            .Where(wearable => !userProfile.HasEquipped(wearable.id)
 1021                               && wearable.ThirdPartyCollectionId == collectionId)
 01022            .Select(item => item.id)
 1023            .ToList();
 01024        CatalogController.i.Remove(wearablesToRemove);
 01025        thirdPartyCollectionsActive.Remove(collectionId);
 1026
 01027        foreach (string wearableId in wearablesToRemove)
 1028        {
 01029            userProfile.RemoveFromInventory(wearableId);
 01030            thirdPartyWearablesLoaded.Remove(wearableId);
 1031        }
 1032
 01033        LoadUserProfile(userProfile, true);
 01034    }
 1035
 1036    private bool ShouldShowHideOtherWearablesToast(WearableItem wearable)
 1037    {
 01038        var isWearingSkinAlready = model.wearables.Any(item => item.IsSkin());
 01039        return wearable.IsSkin() && !isWearingSkinAlready;
 1040    }
 1041
 1042    private bool ShouldShowIncompatibleWearableToast(WearableItem wearable)
 1043    {
 01044        if(wearable.data.category == WearableLiterals.Categories.BODY_SHAPE || wearable.data.category == WearableLiteral
 01045            return false;
 1046        else
 01047            return !wearable.SupportsBodyShape(model.bodyShape.id);
 1048    }
 1049
 1050    private bool IsTryingToReplaceSkin(WearableItem wearable)
 1051    {
 121052        return model.wearables.Any(skin =>
 1053        {
 801054            return skin.IsSkin()
 1055                   && skin.DoesHide(wearable.data.category, model.bodyShape.id);
 1056        });
 1057    }
 1058
 1059    private bool ShouldShowReplaceOtherWearablesToast(WearableItem wearable)
 1060    {
 01061        if (IsTryingToReplaceSkin(wearable)) return true;
 01062        var toReplace = GetWearablesReplacedBy(wearable);
 01063        if (wearable == null || toReplace.Count == 0) return false;
 01064        if (model.wearables.Contains(wearable)) return false;
 1065
 1066        // NOTE: why just 1?
 01067        if (toReplace.Count == 1)
 1068        {
 01069            var w = toReplace[0];
 01070            if (w.data.category == wearable.data.category)
 01071                return false;
 1072        }
 01073        return true;
 1074    }
 1075
 1076
 1077    private void HandleEmotesCostumizationSelection(bool current, bool previous)
 1078    {
 01079        if (!current)
 01080            return;
 1081
 01082        view.sectionSelector.GetSection(AvatarEditorHUDView.EMOTES_SECTION_INDEX).SelectToggle();
 01083    }
 1084
 1085    private void OnNewEmoteAdded(string emoteId)
 1086    {
 01087        if (!isAvatarPreviewReady)
 01088            return;
 1089
 01090        UpdateAvatarPreview();
 01091    }
 1092
 01093    private void OnPreviewEmote(string emoteId) { view.PlayPreviewEmote(emoteId); }
 1094
 1095    private void OnEmoteEquipped(string emoteId)
 1096    {
 01097        catalog.TryGetValue(emoteId, out WearableItem equippedEmote);
 1098
 01099        if (equippedEmote != null)
 01100            EquipEmote(equippedEmote);
 01101    }
 1102
 1103    private void OnEmoteUnequipped(string emoteId)
 1104    {
 01105        catalog.TryGetValue(emoteId, out WearableItem unequippedEmote);
 1106
 01107        if (unequippedEmote != null)
 01108            UnequipEmote(unequippedEmote);
 01109    }
 1110
 01111    private void OnRedirectToEmoteSelling(string emoteId) { SellCollectible(emoteId); }
 1112
 1113    internal void SendNewEquippedWearablesAnalytics(List<string> oldWearables, List<string> newWearables)
 1114    {
 281115        for (int i = 0; i < newWearables.Count; i++)
 1116        {
 121117            if (oldWearables.Contains(newWearables[i]))
 1118                continue;
 1119
 91120            catalog.TryGetValue(newWearables[i], out WearableItem newEquippedEmote);
 91121            if (newEquippedEmote != null && !newEquippedEmote.IsEmote())
 91122                SendEquipWearableAnalytic(newEquippedEmote);
 1123        }
 21124    }
 1125
 1126    private void SendEquipWearableAnalytic(WearableItem equippedWearable)
 1127    {
 91128        Dictionary<string, string> data = new Dictionary<string, string>();
 91129        data.Add("name", equippedWearable.GetName());
 91130        data.Add("rarity", equippedWearable.rarity);
 91131        data.Add("category", equippedWearable.data.category);
 91132        data.Add("linked_wearable", equippedWearable.IsFromThirdPartyCollection.ToString());
 91133        data.Add("third_party_collection_id", equippedWearable.ThirdPartyCollectionId);
 91134        data.Add("is_in_l2", equippedWearable.IsInL2().ToString());
 91135        data.Add("smart_item", equippedWearable.IsSmart().ToString());
 1136
 91137        analytics.SendAnalytic(EQUIP_WEARABLE_METRIC, data);
 91138    }
 1139
 481140    internal virtual IEmotesCustomizationComponentController CreateEmotesController() => new EmotesCustomizationComponen
 1141
 1142    private bool IsWearableUpdateInCooldown()
 1143    {
 01144        return Time.realtimeSinceStartup < lastTimeOwnedWearablesChecked + ownedWearableEmotesRequestRetryTime;
 1145    }
 1146
 1147    private bool IsEmotesUpdateInCooldown()
 1148    {
 431149        return Time.realtimeSinceStartup < lastTimeOwnedEmotesChecked + ownedWearableEmotesRequestRetryTime;
 1150    }
 1151
 1152    private bool AreWearablesAlreadyLoaded()
 1153    {
 01154        return ownedWearablesAlreadyLoaded || ownedWearablesRemainingRequests <= 0;
 1155    }
 1156
 1157    private void OnApplicationFocus()
 1158    {
 01159        lastTimeOwnedWearablesChecked = -ownedWearableEmotesRequestRetryTime;
 01160        lastTimeOwnedEmotesChecked = -ownedWearableEmotesRequestRetryTime;
 01161    }
 1162}

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])
OnAdditionalWearableRemoved(System.String, WearableItem)
OnAdditionalWearableAdded(System.String, WearableItem)
LoadUserProfile(UserProfile)
LoadOwnedWereables(UserProfile)
LoadOwnedEmotes()
LoadOwnedEmotesTask()
QueryNftCollections(System.String)
ReloadOwnedWearablesOnRefocus()
PlayerRendererLoaded(System.Boolean, System.Boolean)
LoadUserProfile(UserProfile, System.Boolean)
EnsureWearablesCategoriesNotEmpty()
WearableClicked(System.String)
HairColorClicked(UnityEngine.Color)
SkinColorClicked(UnityEngine.Color)
EyesColorClicked(UnityEngine.Color)
UpdateAvatarPreview()
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)
ShouldShowIncompatibleWearableToast(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()
IsWearableUpdateInCooldown()
IsEmotesUpdateInCooldown()
AreWearablesAlreadyLoaded()
OnApplicationFocus()