< 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:379
Uncovered lines:196
Coverable lines:575
Total lines:1163
Line coverage:65.9% (379 of 575)
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%74.3617041.67%
QueryNftCollections(...)0%7.464040%
ReloadOwnedWearablesOnRefocus()0%2100%
PlayerRendererLoaded(...)0%6.136084.62%
LoadUserProfile(...)0%13.6113084.62%
EnsureWearablesCategoriesNotEmpty()0%8.038092.31%
WearableClicked(...)0%8.198085.71%
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.027092.86%
SetVisibility(...)0%110100%
OnAvatarEditorVisibleChanged(...)0%110100%
SetVisibility_Internal(...)0%15.8915084.21%
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;
 8540    bool renderingEnabled => CommonScriptableObjects.rendererState.Get();
 8141    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;
 34145    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);
 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    {
 22180        LoadUserProfile(userProfile, false);
 22181        QueryNftCollections(userProfile.userId);
 22182    }
 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);
 217                             }
 218                             else
 219                             {
 0220                                 LoadUserProfile(userProfile, true);
 0221                                 NotificationsController.i.ShowNotification(new Model
 222                                 {
 223                                     message = LOADING_OWNED_WEARABLES_ERROR_MESSAGE,
 224                                     type = Type.GENERIC,
 225                                     timer = 10f,
 226                                     destroyOnFinish = true
 227                                 });
 228
 0229                                 Debug.LogError(error);
 0230                                 loadingWearables = false;
 231                             }
 0232                         });
 0233    }
 234
 235    private void LoadOwnedEmotes()
 236    {
 237        //Only check emotes once every 60 seconds
 43238        if (IsEmotesUpdateInCooldown())
 0239            return;
 240
 43241        lastTimeOwnedEmotesChecked = Time.realtimeSinceStartup;
 242        //TODO only request OwnedEmotes once every minute
 43243        loadEmotesCTS?.Cancel();
 43244        loadEmotesCTS?.Dispose();
 43245        loadEmotesCTS = null;
 246        // we only follow this flow with new profiles
 43247        if (userProfile?.avatar != null)
 248        {
 43249            loadEmotesCTS = new CancellationTokenSource();
 43250            LoadOwnedEmotesTask(loadEmotesCTS.Token);
 251        }
 43252    }
 253
 254    private async UniTaskVoid LoadOwnedEmotesTask(CancellationToken ct = default, int retries = LOADING_OWNED_WEARABLES_
 255    {
 43256        var emotesCatalog = Environment.i.serviceLocator.Get<IEmotesCatalogService>();
 257        try
 258        {
 43259            var embeddedEmotes = Resources.Load<EmbeddedEmotesSO>("EmbeddedEmotes").emotes;
 43260            var emotes = await emotesCatalog.RequestOwnedEmotesAsync(userProfile.userId, ct);
 43261            var emotesList = emotes == null ? embeddedEmotes.Cast<WearableItem>().ToList() : emotes.Concat(embeddedEmote
 43262            var emotesFilter = new HashSet<string>();
 2752263            foreach (var e in emotesList)
 1333264                emotesFilter.Add(e.id);
 265
 43266            if(loadingWearables)
 0267                await UniTask.WaitWhile(() => loadingWearables, cancellationToken: ct);
 268
 43269            if (emotesLoadedAsWearables != null)
 270            {
 0271                foreach (var emoteAsWearable in emotesLoadedAsWearables)
 272                {
 0273                    if (emotesFilter.Contains(emoteAsWearable.id))
 274                        continue;
 275
 0276                    emotesList.Add(emoteAsWearable);
 277                }
 278
 0279                emotesLoadedAsWearables = null;
 280            }
 281
 43282            emotesCustomizationDataStore.UnequipMissingEmotes(emotesList);
 43283            emotesCustomizationComponentController.SetEmotes(emotesList.ToArray());
 284
 43285        }
 0286        catch (Exception e)
 287        {
 0288            OperationCanceledException opCanceled = e as OperationCanceledException;
 289            // If the cancellation was requested upwards, dont retry
 0290            if (opCanceled != null && ct.IsCancellationRequested)
 0291                return;
 292
 0293            if (retries > 0)
 294            {
 0295                LoadOwnedEmotesTask(ct, retries - 1);
 296            }
 297            else
 298            {
 0299                if (opCanceled == null) // Ignore operation canceled exceptions when logging
 0300                    Debug.LogWarning(e.ToString());
 301                const string ERROR = "There was a problem loading your emotes";
 0302                NotificationsController.i.ShowNotification(new Model
 303                {
 304                    message = ERROR,
 305                    type = Type.GENERIC,
 306                    timer = 10f,
 307                    destroyOnFinish = true
 308                });
 309            }
 0310        }
 43311    }
 312
 313    private void QueryNftCollections(string userId)
 314    {
 22315        if (string.IsNullOrEmpty(userId))
 22316            return;
 317
 0318        Environment.i.platform.serviceProviders.theGraph.QueryNftCollections(userProfile.userId, NftCollectionsLayer.ETH
 0319           .Then((nfts) => ownedNftCollectionsL1 = nfts)
 0320           .Catch((error) => Debug.LogError(error));
 321
 0322        Environment.i.platform.serviceProviders.theGraph.QueryNftCollections(userProfile.userId, NftCollectionsLayer.MAT
 0323           .Then((nfts) => ownedNftCollectionsL2 = nfts)
 0324           .Catch((error) => Debug.LogError(error));
 0325    }
 326
 327    public void ReloadOwnedWearablesOnRefocus()
 328    {
 0329        ownedWearablesRemainingRequests = LOADING_OWNED_WEARABLES_RETRIES;
 0330        LoadOwnedWereables(userProfile);
 0331        LoadOwnedEmotes();
 0332    }
 333
 334    private void PlayerRendererLoaded(bool current, bool previous)
 335    {
 15336        if (!current)
 0337            return;
 338
 15339        if (!ownedWearablesAlreadyLoaded)
 340        {
 15341            List<string> equippedOwnedWearables = new List<string>();
 78342            for (int i = 0; i < userProfile.avatar.wearables.Count; i++)
 343            {
 24344                if (catalog.TryGetValue(userProfile.avatar.wearables[i], out WearableItem wearable) &&
 345                    !wearable.data.tags.Contains(WearableLiterals.Tags.BASE_WEARABLE))
 346                {
 0347                    equippedOwnedWearables.Add(userProfile.avatar.wearables[i]);
 348                }
 349            }
 350
 15351            userProfile.SetInventory(equippedOwnedWearables.ToArray());
 352        }
 353
 15354        LoadUserProfile(userProfile, true);
 15355        DataStore.i.common.isPlayerRendererLoaded.OnChange -= PlayerRendererLoaded;
 15356    }
 357
 358    public void LoadUserProfile(UserProfile userProfile, bool forceLoading)
 359    {
 85360        bool avatarEditorNotVisible = renderingEnabled && !view.isOpen;
 85361        bool isPlaying = !Application.isBatchMode;
 362
 85363        if (!forceLoading)
 364        {
 22365            if (isPlaying && avatarEditorNotVisible)
 0366                return;
 367        }
 368
 85369        if (userProfile == null)
 0370            return;
 371
 85372        if (userProfile.avatar == null || string.IsNullOrEmpty(userProfile.avatar.bodyShape))
 3373            return;
 374
 82375        view.InitializeNavigationEvents(!userProfile.hasConnectedWeb3);
 376
 82377        CatalogController.wearableCatalog.TryGetValue(userProfile.avatar.bodyShape, out var bodyShape);
 378
 82379        if (bodyShape == null)
 380        {
 0381            return;
 382        }
 383
 82384        view.SetIsWeb3(userProfile.hasConnectedWeb3);
 385
 82386        ProcessCatalog(this.catalog);
 387
 82388        if (avatarIsDirty)
 1389            return;
 390
 81391        EquipBodyShape(bodyShape);
 81392        EquipSkinColor(userProfile.avatar.skinColor);
 81393        EquipHairColor(userProfile.avatar.hairColor);
 81394        EquipEyesColor(userProfile.avatar.eyeColor);
 395
 81396        model.wearables.Clear();
 81397        view.UnselectAllWearables();
 398
 81399        int wearablesCount = userProfile.avatar.wearables.Count;
 400
 81401        if (isPlayerRendererLoaded)
 402        {
 138403            for (var i = 0; i < wearablesCount; i++)
 404            {
 46405                CatalogController.wearableCatalog.TryGetValue(userProfile.avatar.wearables[i], out var wearable);
 46406                if (wearable == null)
 407                {
 0408                    Debug.LogError($"Couldn't find wearable with ID {userProfile.avatar.wearables[i]}");
 0409                    continue;
 410                }
 411
 46412                if (wearable.IsEmote())
 0413                    EquipEmote(wearable);
 414                else
 46415                    EquipWearable(wearable);
 416            }
 417        }
 418
 81419        EnsureWearablesCategoriesNotEmpty();
 420
 81421        UpdateAvatarPreview();
 81422        isAvatarPreviewReady = true;
 81423    }
 424
 425    private void EnsureWearablesCategoriesNotEmpty()
 426    {
 81427        var categoriesInUse = model.wearables
 46428            .Where(x => !x.IsEmote())
 46429            .Select(x => x.data.category).ToArray();
 430
 1296431        for (var i = 0; i < categoriesThatMustHaveSelection.Length; i++)
 432        {
 567433            var category = categoriesThatMustHaveSelection[i];
 567434            if (category != Categories.BODY_SHAPE && !(categoriesInUse.Contains(category)))
 435            {
 436                WearableItem wearable;
 450437                var defaultItemId = WearableLiterals.DefaultWearables.GetDefaultWearable(model.bodyShape.id, category);
 450438                if (defaultItemId != null)
 439                {
 450440                    CatalogController.wearableCatalog.TryGetValue(defaultItemId, out wearable);
 441                }
 442                else
 443                {
 0444                    wearable = wearablesByCategory[category].FirstOrDefault(x => x.SupportsBodyShape(model.bodyShape.id)
 445                }
 446
 450447                if (wearable != null)
 448                {
 450449                    EquipWearable(wearable);
 450                }
 451            }
 452        }
 81453    }
 454
 455    public void WearableClicked(string wearableId)
 456    {
 20457        CatalogController.wearableCatalog.TryGetValue(wearableId, out var wearable);
 23458        if (wearable == null) return;
 459
 17460        if (wearable.data.category == Categories.BODY_SHAPE)
 461        {
 3462            if (wearable.id == model.bodyShape.id)
 0463                return;
 3464            EquipBodyShape(wearable);
 465        }
 466        else
 467        {
 14468            if (model.wearables.Contains(wearable))
 469            {
 2470                if (!categoriesThatMustHaveSelection.Contains(wearable.data.category))
 471                {
 0472                    UnequipWearable(wearable);
 473                }
 474                else
 475                {
 2476                    return;
 477                }
 478            }
 479            else
 480            {
 12481                if (IsTryingToReplaceSkin(wearable))
 0482                    UnequipWearable(model.GetWearable(Categories.SKIN));
 483
 12484                var sameCategoryEquipped = model.GetWearable(wearable.data.category);
 12485                if (sameCategoryEquipped != null)
 2486                    UnequipWearable(sameCategoryEquipped);
 487
 12488                EquipWearable(wearable);
 489            }
 490        }
 491
 15492        UpdateAvatarPreview();
 15493        view.AddFeedbackOnAppear();
 15494        avatarIsDirty = true;
 15495    }
 496
 497    public void HairColorClicked(Color color)
 498    {
 2499        EquipHairColor(color);
 2500        view.SelectHairColor(model.hairColor);
 2501        UpdateAvatarPreview();
 2502        view.AddFeedbackOnAppear();
 2503    }
 504
 505    public void SkinColorClicked(Color color)
 506    {
 2507        EquipSkinColor(color);
 2508        view.SelectSkinColor(model.skinColor);
 2509        UpdateAvatarPreview();
 2510    }
 511
 512    public void EyesColorClicked(Color color)
 513    {
 2514        EquipEyesColor(color);
 2515        view.SelectEyeColor(model.eyesColor);
 2516        UpdateAvatarPreview();
 2517    }
 518
 519    protected virtual void UpdateAvatarPreview()
 520    {
 103521        if (bypassUpdateAvatarPreview)
 0522            return;
 523
 103524        AvatarModel modelToUpdate = model.ToAvatarModel();
 525
 526        // We always keep the loaded emotes into the Avatar Preview
 6592527        foreach (string emoteId in emotesCustomizationDataStore.currentLoadedEmotes.Get())
 528        {
 3193529            modelToUpdate.emotes.Add(new AvatarModel.AvatarEmoteEntry() { urn = emoteId });
 530        }
 531
 103532        view.UpdateAvatarPreview(modelToUpdate);
 103533    }
 534
 535    private void EquipHairColor(Color color)
 536    {
 84537        model.hairColor = color;
 84538        view.SelectHairColor(model.hairColor);
 84539    }
 540
 541    private void EquipEyesColor(Color color)
 542    {
 84543        model.eyesColor = color;
 84544        view.SelectEyeColor(model.eyesColor);
 84545    }
 546
 547    private void EquipSkinColor(Color color)
 548    {
 83549        model.skinColor = color;
 83550        view.SelectSkinColor(model.skinColor);
 83551    }
 552
 553    private void EquipBodyShape(WearableItem bodyShape)
 554    {
 84555        if (bodyShape.data.category != Categories.BODY_SHAPE)
 556        {
 0557            Debug.LogError($"Item ({bodyShape.id} is not a body shape");
 0558            return;
 559        }
 560
 84561        if (model.bodyShape == bodyShape)
 22562            return;
 563
 62564        model.bodyShape = bodyShape;
 62565        emotesCustomizationComponentController.SetEquippedBodyShape(bodyShape.id);
 62566        view.UpdateSelectedBody(bodyShape);
 567
 62568        int wearablesCount = model.wearables.Count;
 182569        for (var i = wearablesCount - 1; i >= 0; i--)
 570        {
 29571            UnequipWearable(model.wearables[i]);
 572        }
 573
 62574        var defaultWearables = WearableLiterals.DefaultWearables.GetDefaultWearables(bodyShape.id);
 1022575        for (var i = 0; i < defaultWearables.Length; i++)
 576        {
 449577            if (catalog.TryGetValue(defaultWearables[i], out var wearable))
 449578                EquipWearable(wearable);
 579        }
 62580    }
 581
 582    private void EquipWearable(WearableItem wearable)
 583    {
 964584        if (wearable.IsEmote())
 0585            return;
 586
 964587        if (!wearablesByCategory.ContainsKey(wearable.data.category))
 0588            return;
 589
 964590        if (wearablesByCategory[wearable.data.category].Contains(wearable) && wearable.SupportsBodyShape(model.bodyShape
 591        {
 964592            var toReplace = GetWearablesReplacedBy(wearable);
 964593            toReplace.ForEach(UnequipWearable);
 964594            model.wearables.Add(wearable);
 964595            view.EquipWearable(wearable);
 596        }
 964597    }
 598
 599    private void UnequipWearable(WearableItem wearable)
 600    {
 33601        if (wearable.IsEmote())
 0602            return;
 603
 33604        if (model.wearables.Contains(wearable))
 605        {
 33606            model.wearables.Remove(wearable);
 33607            view.UnequipWearable(wearable);
 33608            avatarIsDirty = true;
 609        }
 33610    }
 611
 612    private void EquipEmote(WearableItem emote)
 613    {
 0614        if (!emote.IsEmote())
 0615            return;
 0616        avatarIsDirty = true;
 0617    }
 618
 619    private void UnequipEmote(WearableItem emote)
 620    {
 0621        if (!emote.IsEmote())
 0622            return;
 623
 0624        avatarIsDirty = true;
 0625    }
 626
 627    public void UnequipAllWearables()
 628    {
 892629        foreach (var wearable in model.wearables)
 630        {
 371631            if (!wearable.IsEmote())
 371632                view.UnequipWearable(wearable);
 633        }
 634
 75635        model.wearables.Clear();
 75636    }
 637
 638    private void ProcessCatalog(BaseDictionary<string, WearableItem> catalog)
 639    {
 130640        wearablesByCategory.Clear();
 130641        view.RemoveAllWearables();
 130642        bool hasSkin = false;
 130643        bool hasCollectible = false;
 130644        using (var iterator = catalog.Get().GetEnumerator())
 645        {
 4774646            while (iterator.MoveNext())
 647            {
 4644648                if (iterator.Current.Value.IsEmote())
 649                    continue;
 650
 4644651                if (iterator.Current.Value.IsFromThirdPartyCollection
 652                    && !thirdPartyCollectionsActive.Contains(iterator.Current.Value.ThirdPartyCollectionId))
 653                    continue;
 654
 4644655                AddWearable(iterator.Current.Key, iterator.Current.Value);
 4644656                hasSkin = iterator.Current.Value.IsSkin() || hasSkin;
 4644657                hasCollectible = iterator.Current.Value.IsCollectible() || hasCollectible;
 658            }
 130659        }
 130660        view.ShowSkinPopulatedList(hasSkin);
 130661        view.ShowCollectiblesPopulatedList(hasCollectible);
 130662        view.RefreshSelectorsSize();
 130663    }
 664
 665    private void AddWearable(string id, WearableItem wearable)
 666    {
 4655667        if (!wearable.data.tags.Contains(WearableLiterals.Tags.BASE_WEARABLE) && userProfile.GetItemAmount(id) == 0)
 1022668            return;
 669
 3633670        if (!wearablesByCategory.ContainsKey(wearable.data.category))
 1419671            wearablesByCategory.Add(wearable.data.category, new List<WearableItem>());
 672
 3633673        wearablesByCategory[wearable.data.category].Add(wearable);
 3633674        view.AddWearable(wearable, userProfile.GetItemAmount(id),
 675            ShouldShowHideOtherWearablesToast,
 676            ShouldShowReplaceOtherWearablesToast,
 677            ShouldShowIncompatibleWearableToast);
 3633678    }
 679
 680    private void RemoveWearable(string id, WearableItem wearable)
 681    {
 0682        if (wearablesByCategory.ContainsKey(wearable.data.category))
 683        {
 0684            if (wearablesByCategory[wearable.data.category].Remove(wearable))
 685            {
 0686                if (wearablesByCategory[wearable.data.category].Count == 0)
 687                {
 0688                    wearablesByCategory.Remove(wearable.data.category);
 689                }
 690            }
 691        }
 692
 0693        view.RemoveWearable(wearable);
 0694    }
 695
 696    public void RandomizeWearables()
 697    {
 1698        EquipHairColor(view.GetRandomColor());
 1699        EquipEyesColor(view.GetRandomColor());
 700
 7701        List<WearableItem> wearablesToRemove = model.wearables.Where(x => !x.IsEmote()).ToList();
 14702        foreach (var wearable in wearablesToRemove)
 703        {
 6704            model.wearables.Remove(wearable);
 705        }
 706
 1707        view.UnselectAllWearables();
 1708        using (var iterator = wearablesByCategory.GetEnumerator())
 709        {
 12710            while (iterator.MoveNext())
 711            {
 11712                string category = iterator.Current.Key;
 11713                if (!categoriesToRandomize.Contains(category))
 714                {
 715                    continue;
 716                }
 717
 29718                var supportedWearables = iterator.Current.Value.Where(x => x.SupportsBodyShape(model.bodyShape.id)).ToAr
 7719                if (supportedWearables.Length == 0)
 720                {
 0721                    Debug.LogError($"Couldn't get any wearable for category {category} and bodyshape {model.bodyShape.id
 722                }
 723
 7724                var wearable = supportedWearables[Random.Range(0, supportedWearables.Length - 1)];
 7725                EquipWearable(wearable);
 726            }
 1727        }
 728
 1729        UpdateAvatarPreview();
 1730        view.AddFeedbackOnAppear();
 1731    }
 732
 733    private List<WearableItem> GetWearablesReplacedBy(WearableItem wearableItem)
 734    {
 964735        var wearablesToReplace = new List<WearableItem>();
 964736        var categoriesToReplace = new HashSet<string>(wearableItem.GetReplacesList(model.bodyShape.id) ?? new string[0])
 737
 964738        int wearableCount = model.wearables.Count;
 7496739        for (int i = 0; i < wearableCount; i++)
 740        {
 2784741            var wearable = model.wearables[i];
 2784742            if (wearable == null) continue;
 743
 2784744            if (categoriesToReplace.Contains(wearable.data.category))
 745            {
 2746                wearablesToReplace.Add(wearable);
 747            }
 748            else
 749            {
 750                //For retrocompatibility's sake we check current wearables against new one (compatibility matrix is symm
 2782751                HashSet<string> replacesList = new HashSet<string>(wearable.GetReplacesList(model.bodyShape.id) ?? new s
 2782752                if (replacesList.Contains(wearableItem.data.category))
 753                {
 0754                    wearablesToReplace.Add(wearable);
 755                }
 756            }
 757        }
 758
 964759        return wearablesToReplace;
 760    }
 761
 88762    public void SetVisibility(bool visible) { avatarEditorVisible.Set(visible); }
 763
 184764    private void OnAvatarEditorVisibleChanged(bool current, bool previous) { SetVisibility_Internal(current); }
 765
 766    private void SetVisibility_Internal(bool visible)
 767    {
 92768        bool isSignUpFlow = DataStore.i.common.isSignUpFlow.Get();
 92769        if (!visible && view.isOpen)
 770        {
 1771            view.ResetPreviewEmote();
 772
 1773            if (isSignUpFlow)
 0774                DataStore.i.virtualAudioMixer.sceneSFXVolume.Set(1f);
 775
 1776            Environment.i.messaging.manager.paused = false;
 1777            DataStore.i.skyboxConfig.avatarMatProfile.Set(AvatarMaterialProfile.InWorld);
 1778            if (prevMouseLockState && isSignUpFlow)
 779            {
 0780                Utils.LockCursor();
 781            }
 782
 783            // NOTE(Brian): SSAO doesn't work correctly with the offseted avatar preview if the renderScale != 1.0
 1784            var asset = GraphicsSettings.renderPipelineAsset as UniversalRenderPipelineAsset;
 1785            asset.renderScale = prevRenderScale;
 786
 1787            if (isSignUpFlow)
 0788                CommonScriptableObjects.isFullscreenHUDOpen.Set(false);
 789
 1790            DataStore.i.common.isPlayerRendererLoaded.OnChange -= PlayerRendererLoaded;
 791
 1792            OnClose?.Invoke();
 793        }
 91794        else if (visible && !view.isOpen)
 795        {
 43796            if (isSignUpFlow)
 797            {
 0798                DataStore.i.virtualAudioMixer.sceneSFXVolume.Set(0f);
 0799                view.sectionSelector.Hide(true);
 800            }
 801            else
 802            {
 43803                view.sectionSelector.Show(true);
 804            }
 805
 43806            LoadOwnedWereables(userProfile);
 43807            if (!isSignUpFlow)
 43808                LoadOwnedEmotes();
 809
 43810            LoadCollections();
 43811            Environment.i.messaging.manager.paused = isSignUpFlow;
 43812            DataStore.i.skyboxConfig.avatarMatProfile.Set(AvatarMaterialProfile.InEditor);
 813
 43814            prevMouseLockState = Utils.IsCursorLocked;
 815
 43816            if (isSignUpFlow || !DataStore.i.exploreV2.isInitialized.Get())
 43817                Utils.UnlockCursor();
 818
 819            // NOTE(Brian): SSAO doesn't work correctly with the offseted avatar preview if the renderScale != 1.0
 43820            var asset = GraphicsSettings.renderPipelineAsset as UniversalRenderPipelineAsset;
 43821            prevRenderScale = asset.renderScale;
 43822            asset.renderScale = 1.0f;
 823
 43824            if (isSignUpFlow)
 0825                CommonScriptableObjects.isFullscreenHUDOpen.Set(true);
 826
 43827            DataStore.i.common.isPlayerRendererLoaded.OnChange += PlayerRendererLoaded;
 828
 43829            OnOpen?.Invoke();
 830        }
 831
 92832        view.SetVisibility(visible);
 92833    }
 834
 835    public void Dispose()
 836    {
 43837        loadEmotesCTS?.Cancel();
 43838        loadEmotesCTS?.Dispose();
 43839        loadEmotesCTS = null;
 840
 43841        avatarEditorVisible.OnChange -= OnAvatarEditorVisibleChanged;
 43842        configureBackpackInFullscreenMenu.OnChange -= ConfigureBackpackInFullscreenMenuChanged;
 43843        DataStore.i.common.isPlayerRendererLoaded.OnChange -= PlayerRendererLoaded;
 43844        exploreV2IsOpen.OnChange -= ExploreV2IsOpenChanged;
 43845        emotesCustomizationDataStore.isEmotesCustomizationSelected.OnChange -= HandleEmotesCostumizationSelection;
 43846        emotesCustomizationDataStore.currentLoadedEmotes.OnAdded -= OnNewEmoteAdded;
 847
 43848        emotesCustomizationComponentController.onEmotePreviewed -= OnPreviewEmote;
 43849        emotesCustomizationComponentController.onEmoteEquipped -= OnEmoteEquipped;
 43850        emotesCustomizationComponentController.onEmoteUnequipped -= OnEmoteUnequipped;
 43851        emotesCustomizationComponentController.onEmoteSell -= OnRedirectToEmoteSelling;
 852
 43853        avatarEditorHUDAnimationController.Dispose();
 43854        Environment.i.serviceLocator.Get<IApplicationFocusService>().OnApplicationFocus -= OnApplicationFocus;
 855
 43856        CleanUp();
 43857    }
 858
 859    public void CleanUp()
 860    {
 48861        UnequipAllWearables();
 862
 48863        if (view != null)
 48864            view.Dispose();
 865
 48866        this.userProfile.OnUpdate -= LoadUserProfile;
 48867        this.catalog.OnAdded -= AddWearable;
 48868        this.catalog.OnRemoved -= RemoveWearable;
 48869        DataStore.i.common.isPlayerRendererLoaded.OnChange -= PlayerRendererLoaded;
 48870    }
 871
 0872    public void SetConfiguration(HUDConfiguration configuration) { SetVisibility(configuration.active); }
 873
 874    public void SaveAvatar(Texture2D face256Snapshot, Texture2D bodySnapshot)
 875    {
 1876        var avatarModel = model.ToAvatarModel();
 877
 878        // Add the equipped emotes to the avatar model
 1879        List<AvatarModel.AvatarEmoteEntry> emoteEntries = new List<AvatarModel.AvatarEmoteEntry>();
 1880        int equippedEmotesCount = emotesCustomizationDataStore.unsavedEquippedEmotes.Count();
 22881        for (int i = 0; i < equippedEmotesCount; i++)
 882        {
 10883            var equippedEmote = emotesCustomizationDataStore.unsavedEquippedEmotes[i];
 10884            if (equippedEmote == null)
 885                continue;
 886
 0887            emoteEntries.Add(new AvatarModel.AvatarEmoteEntry { slot = i, urn = equippedEmote.id });
 888        }
 889
 1890        avatarModel.emotes = emoteEntries;
 891
 1892        SendNewEquippedWearablesAnalytics(userProfile.avatar.wearables, avatarModel.wearables);
 1893        emotesCustomizationDataStore.equippedEmotes.Set(emotesCustomizationDataStore.unsavedEquippedEmotes.Get());
 894
 1895        WebInterface.SendSaveAvatar(avatarModel, face256Snapshot, bodySnapshot, DataStore.i.common.isSignUpFlow.Get());
 1896        userProfile.OverrideAvatar(avatarModel, face256Snapshot);
 897
 1898        if (DataStore.i.common.isSignUpFlow.Get())
 899        {
 0900            DataStore.i.HUDs.signupVisible.Set(true);
 0901            newUserExperienceAnalytics.AvatarEditSuccessNux();
 902        }
 903
 1904        avatarIsDirty = false;
 1905        SetVisibility(false);
 1906    }
 907
 908    public void GoToMarketplaceOrConnectWallet()
 909    {
 0910        if (userProfile.hasConnectedWeb3)
 0911            WebInterface.OpenURL(URL_MARKET_PLACE);
 912        else
 0913            WebInterface.OpenURL(URL_GET_A_WALLET);
 0914    }
 915
 916    public void SellCollectible(string collectibleId)
 917    {
 0918        var ownedCollectible = ownedNftCollectionsL1.FirstOrDefault(nft => nft.urn == collectibleId);
 0919        if (ownedCollectible == null)
 0920            ownedCollectible = ownedNftCollectionsL2.FirstOrDefault(nft => nft.urn == collectibleId);
 921
 0922        if (ownedCollectible != null)
 0923            WebInterface.OpenURL(URL_SELL_SPECIFIC_COLLECTIBLE.Replace("{collectionId}", ownedCollectible.collectionId).
 924        else
 0925            WebInterface.OpenURL(URL_SELL_COLLECTIBLE_GENERIC);
 0926    }
 927
 0928    public void ToggleVisibility() { SetVisibility(!view.isOpen); }
 929
 96930    private void ConfigureBackpackInFullscreenMenuChanged(Transform currentParentTransform, Transform previousParentTran
 931
 932    private void ExploreV2IsOpenChanged(bool current, bool previous)
 933    {
 0934        if (!current && avatarIsDirty)
 935        {
 0936            avatarIsDirty = false;
 937
 0938            LoadUserProfile(userProfile, true);
 939
 0940            emotesCustomizationComponentController.RestoreEmoteSlots();
 941        }
 0942    }
 943
 944    private void LoadCollections()
 945    {
 43946        if (!isThirdPartyCollectionsEnabled || collectionsAlreadyLoaded)
 43947            return;
 948
 0949        WearablesFetchingHelper.GetThirdPartyCollections()
 950            .Then((collections) =>
 951            {
 0952                view.LoadCollectionsDropdown(collections);
 0953                collectionsAlreadyLoaded = true;
 0954                LoadUserThirdPartyWearables();
 0955            })
 0956            .Catch((error) => Debug.LogError(error));
 0957    }
 958
 959    private void LoadUserThirdPartyWearables()
 960    {
 0961        List<string> collectionIdsToLoad = new List<string>();
 0962        foreach (string wearableId in userProfile.avatar.wearables)
 963        {
 0964            CatalogController.wearableCatalog.TryGetValue(wearableId, out var wearable);
 965
 0966            if (wearable != null && wearable.IsFromThirdPartyCollection)
 967            {
 0968                if (!collectionIdsToLoad.Contains(wearable.ThirdPartyCollectionId))
 0969                    collectionIdsToLoad.Add(wearable.ThirdPartyCollectionId);
 970            }
 971        }
 972
 0973        foreach (string collectionId in collectionIdsToLoad)
 974        {
 0975            view.ToggleThirdPartyCollection(collectionId, true);
 976        }
 0977    }
 978
 979    public void ToggleThirdPartyCollection(bool isOn, string collectionId, string collectionName)
 980    {
 1981        if (isOn)
 1982            FetchAndShowThirdPartyCollection(collectionId, collectionName);
 983        else
 0984            RemoveThirdPartyCollection(collectionId);
 0985    }
 986
 987    private void FetchAndShowThirdPartyCollection(string collectionId, string collectionName)
 988    {
 1989        view.BlockCollectionsDropdown(true);
 1990        CatalogController.RequestThirdPartyWearablesByCollection(userProfile.userId, collectionId)
 991            .Then(wearables =>
 992            {
 0993                wearables = wearables.Where(x => x.ThirdPartyCollectionId == collectionId).ToArray();
 0994                if (wearables.Count().Equals(0)) view.ShowNoItemOfWearableCollectionWarning();
 0995                thirdPartyCollectionsActive.Add(collectionId);
 0996                foreach (var wearable in wearables)
 997                {
 0998                    if (!userProfile.ContainsInInventory(wearable.id))
 999                    {
 01000                        userProfile.AddToInventory(wearable.id);
 1001
 01002                        if (!thirdPartyWearablesLoaded.Contains(wearable.id))
 01003                            thirdPartyWearablesLoaded.Add(wearable.id);
 1004                    }
 1005                }
 1006
 01007                view.BlockCollectionsDropdown(false);
 01008                LoadUserProfile(userProfile, true);
 01009                view.RefreshSelectorsSize();
 01010            })
 1011            .Catch((error) =>
 1012            {
 01013                view.BlockCollectionsDropdown(false);
 01014                Debug.LogError(error);
 01015            });
 11016    }
 1017
 1018    private void RemoveThirdPartyCollection(string collectionId)
 1019    {
 01020        var wearablesToRemove = CatalogController.i.Wearables.GetValues()
 01021            .Where(wearable => !userProfile.HasEquipped(wearable.id)
 1022                               && wearable.ThirdPartyCollectionId == collectionId)
 01023            .Select(item => item.id)
 1024            .ToList();
 01025        CatalogController.i.Remove(wearablesToRemove);
 01026        thirdPartyCollectionsActive.Remove(collectionId);
 1027
 01028        foreach (string wearableId in wearablesToRemove)
 1029        {
 01030            userProfile.RemoveFromInventory(wearableId);
 01031            thirdPartyWearablesLoaded.Remove(wearableId);
 1032        }
 1033
 01034        LoadUserProfile(userProfile, true);
 01035    }
 1036
 1037    private bool ShouldShowHideOtherWearablesToast(WearableItem wearable)
 1038    {
 01039        var isWearingSkinAlready = model.wearables.Any(item => item.IsSkin());
 01040        return wearable.IsSkin() && !isWearingSkinAlready;
 1041    }
 1042
 1043    private bool ShouldShowIncompatibleWearableToast(WearableItem wearable)
 1044    {
 01045        if(wearable.data.category == WearableLiterals.Categories.BODY_SHAPE || wearable.data.category == WearableLiteral
 01046            return false;
 1047        else
 01048            return !wearable.SupportsBodyShape(model.bodyShape.id);
 1049    }
 1050
 1051    private bool IsTryingToReplaceSkin(WearableItem wearable)
 1052    {
 121053        return model.wearables.Any(skin =>
 1054        {
 801055            return skin.IsSkin()
 1056                   && skin.DoesHide(wearable.data.category, model.bodyShape.id);
 1057        });
 1058    }
 1059
 1060    private bool ShouldShowReplaceOtherWearablesToast(WearableItem wearable)
 1061    {
 01062        if (IsTryingToReplaceSkin(wearable)) return true;
 01063        var toReplace = GetWearablesReplacedBy(wearable);
 01064        if (wearable == null || toReplace.Count == 0) return false;
 01065        if (model.wearables.Contains(wearable)) return false;
 1066
 1067        // NOTE: why just 1?
 01068        if (toReplace.Count == 1)
 1069        {
 01070            var w = toReplace[0];
 01071            if (w.data.category == wearable.data.category)
 01072                return false;
 1073        }
 01074        return true;
 1075    }
 1076
 1077
 1078    private void HandleEmotesCostumizationSelection(bool current, bool previous)
 1079    {
 01080        if (!current)
 01081            return;
 1082
 01083        view.sectionSelector.GetSection(AvatarEditorHUDView.EMOTES_SECTION_INDEX).SelectToggle();
 01084    }
 1085
 1086    private void OnNewEmoteAdded(string emoteId)
 1087    {
 01088        if (!isAvatarPreviewReady)
 01089            return;
 1090
 01091        UpdateAvatarPreview();
 01092    }
 1093
 01094    private void OnPreviewEmote(string emoteId) { view.PlayPreviewEmote(emoteId); }
 1095
 1096    private void OnEmoteEquipped(string emoteId)
 1097    {
 01098        catalog.TryGetValue(emoteId, out WearableItem equippedEmote);
 1099
 01100        if (equippedEmote != null)
 01101            EquipEmote(equippedEmote);
 01102    }
 1103
 1104    private void OnEmoteUnequipped(string emoteId)
 1105    {
 01106        catalog.TryGetValue(emoteId, out WearableItem unequippedEmote);
 1107
 01108        if (unequippedEmote != null)
 01109            UnequipEmote(unequippedEmote);
 01110    }
 1111
 01112    private void OnRedirectToEmoteSelling(string emoteId) { SellCollectible(emoteId); }
 1113
 1114    internal void SendNewEquippedWearablesAnalytics(List<string> oldWearables, List<string> newWearables)
 1115    {
 281116        for (int i = 0; i < newWearables.Count; i++)
 1117        {
 121118            if (oldWearables.Contains(newWearables[i]))
 1119                continue;
 1120
 91121            catalog.TryGetValue(newWearables[i], out WearableItem newEquippedEmote);
 91122            if (newEquippedEmote != null && !newEquippedEmote.IsEmote())
 91123                SendEquipWearableAnalytic(newEquippedEmote);
 1124        }
 21125    }
 1126
 1127    private void SendEquipWearableAnalytic(WearableItem equippedWearable)
 1128    {
 91129        Dictionary<string, string> data = new Dictionary<string, string>();
 91130        data.Add("name", equippedWearable.GetName());
 91131        data.Add("rarity", equippedWearable.rarity);
 91132        data.Add("category", equippedWearable.data.category);
 91133        data.Add("linked_wearable", equippedWearable.IsFromThirdPartyCollection.ToString());
 91134        data.Add("third_party_collection_id", equippedWearable.ThirdPartyCollectionId);
 91135        data.Add("is_in_l2", equippedWearable.IsInL2().ToString());
 91136        data.Add("smart_item", equippedWearable.IsSmart().ToString());
 1137
 91138        analytics.SendAnalytic(EQUIP_WEARABLE_METRIC, data);
 91139    }
 1140
 481141    internal virtual IEmotesCustomizationComponentController CreateEmotesController() => new EmotesCustomizationComponen
 1142
 1143    private bool IsWearableUpdateInCooldown()
 1144    {
 01145        return Time.realtimeSinceStartup < lastTimeOwnedWearablesChecked + ownedWearableEmotesRequestRetryTime;
 1146    }
 1147
 1148    private bool IsEmotesUpdateInCooldown()
 1149    {
 431150        return Time.realtimeSinceStartup < lastTimeOwnedEmotesChecked + ownedWearableEmotesRequestRetryTime;
 1151    }
 1152
 1153    private bool AreWearablesAlreadyLoaded()
 1154    {
 01155        return ownedWearablesAlreadyLoaded || ownedWearablesRemainingRequests <= 0;
 1156    }
 1157
 1158    private void OnApplicationFocus()
 1159    {
 01160        lastTimeOwnedWearablesChecked = -ownedWearableEmotesRequestRetryTime;
 01161        lastTimeOwnedEmotesChecked = -ownedWearableEmotesRequestRetryTime;
 01162    }
 1163}

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()