< 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:377
Uncovered lines:200
Coverable lines:577
Total lines:1156
Line coverage:65.3% (377 of 577)
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.388081.82%
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%
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
 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    {
 4895        this.userProfile = userProfile;
 4896        this.bypassUpdateAvatarPreview = bypassUpdateAvatarPreview;
 97
 4898        view = AvatarEditorHUDView.Create(this);
 99
 48100        view.skinsFeatureContainer.SetActive(true);
 48101        avatarEditorVisible.OnChange += OnAvatarEditorVisibleChanged;
 48102        OnAvatarEditorVisibleChanged(avatarEditorVisible.Get(), false);
 103
 48104        configureBackpackInFullscreenMenu.OnChange += ConfigureBackpackInFullscreenMenuChanged;
 48105        ConfigureBackpackInFullscreenMenuChanged(configureBackpackInFullscreenMenu.Get(), null);
 106
 48107        exploreV2IsOpen.OnChange += ExploreV2IsOpenChanged;
 108
 48109        skinColorList = Resources.Load<ColorList>("SkinTone");
 48110        hairColorList = Resources.Load<ColorList>("HairColor");
 48111        eyeColorList = Resources.Load<ColorList>("EyeColor");
 48112        view.SetColors(skinColorList.colors, hairColorList.colors, eyeColorList.colors);
 113
 48114        SetCatalog(catalog);
 115
 48116        this.userProfile.OnUpdate += LoadUserProfile;
 117
 48118        view.SetSectionActive(AvatarEditorHUDView.EMOTES_SECTION_INDEX, false);
 119
 48120        emotesCustomizationComponentController = CreateEmotesController();
 48121        IEmotesCustomizationComponentView emotesSectionView = emotesCustomizationComponentController.Initialize(
 122            DataStore.i.emotesCustomization,
 123            DataStore.i.emotes,
 124            DataStore.i.exploreV2,
 125            DataStore.i.HUDs);
 126        //Initialize with embedded emotes
 48127        emotesCustomizationComponentController.SetEmotes(EmbeddedEmotesSO.Provide().emotes.ToArray());
 48128        emotesSectionView.viewTransform.SetParent(view.emotesSection.transform, false);
 48129        view.SetSectionActive(AvatarEditorHUDView.EMOTES_SECTION_INDEX, true);
 130
 48131        emotesCustomizationDataStore.isEmotesCustomizationSelected.OnChange += HandleEmotesCostumizationSelection;
 48132        emotesCustomizationDataStore.currentLoadedEmotes.OnAdded += OnNewEmoteAdded;
 133
 48134        emotesCustomizationComponentController.onEmotePreviewed += OnPreviewEmote;
 48135        emotesCustomizationComponentController.onEmoteEquipped += OnEmoteEquipped;
 48136        emotesCustomizationComponentController.onEmoteUnequipped += OnEmoteUnequipped;
 48137        emotesCustomizationComponentController.onEmoteSell += OnRedirectToEmoteSelling;
 138
 48139        LoadUserProfile(userProfile, true);
 140
 48141        DataStore.i.HUDs.isAvatarEditorInitialized.Set(true);
 142
 48143        view.SetThirdPartyCollectionsVisibility(isThirdPartyCollectionsEnabled);
 144
 48145        Environment.i.serviceLocator.Get<IApplicationFocusService>().OnApplicationFocus += OnApplicationFocus;
 48146    }
 147
 148    public void SetCatalog(BaseDictionary<string, WearableItem> catalog)
 149    {
 48150        if (this.catalog != null)
 151        {
 0152            this.catalog.OnAdded -= OnAdditionalWearableAdded;
 0153            this.catalog.OnRemoved -= OnAdditionalWearableRemoved;
 154        }
 155
 48156        this.catalog = catalog;
 157
 48158        ProcessCatalog(this.catalog);
 48159        this.catalog.OnAdded += OnAdditionalWearableAdded;
 48160        this.catalog.OnRemoved += OnAdditionalWearableRemoved;
 48161    }
 162
 163    private void OnAdditionalWearableRemoved(string s, WearableItem item)
 164    {
 0165        RemoveWearable(s, item);
 0166        view.RefreshSelectorsSize();
 0167    }
 168
 169    private void OnAdditionalWearableAdded(string id, WearableItem item)
 170    {
 11171        AddWearable(id, item);
 11172        view.RefreshSelectorsSize();
 11173    }
 174
 175    private void LoadUserProfile(UserProfile userProfile)
 176    {
 43177        LoadUserProfile(userProfile, false);
 43178        QueryNftCollections(userProfile.userId);
 43179    }
 180
 181    private void LoadOwnedWereables(UserProfile userProfile)
 182    {
 183        //If there is no userID we dont fetch owned wearabales
 43184        if(string.IsNullOrEmpty(userProfile.userId))
 43185            return;
 186
 187        // If wearables are loaded, we are in wearable cooldown, we dont fetch again owned wearables
 0188        if (AreWearablesAlreadyLoaded() && IsWearableUpdateInCooldown())
 0189            return;
 190
 0191        lastTimeOwnedWearablesChecked = Time.realtimeSinceStartup;
 192
 0193        loadingWearables = true;
 0194        CatalogController.RequestOwnedWearables(userProfile.userId)
 195                         .Then((ownedWearables) =>
 196                         {
 0197                             ownedWearablesAlreadyLoaded = true;
 198                             //Prior profile V1 emotes must be retrieved along the wearables, onwards they will be reque
 0199                             this.userProfile.SetInventory(ownedWearables.Select(x => x.id).Concat(thirdPartyWearablesLo
 0200                             LoadUserProfile(userProfile, true);
 0201                             if (userProfile != null && userProfile.avatar != null)
 202                             {
 0203                                 emotesLoadedAsWearables = ownedWearables.Where(x => x.IsEmote()).ToArray();
 204                             }
 0205                             loadingWearables = false;
 0206                         })
 207                         .Catch((error) =>
 208                         {
 0209                             ownedWearablesRemainingRequests--;
 0210                             if (ownedWearablesRemainingRequests > 0)
 211                             {
 0212                                 Debug.LogWarning("Retrying owned wereables loading...");
 0213                                 LoadOwnedWereables(userProfile);
 0214                             }
 215                             else
 216                             {
 0217                                 NotificationsController.i.ShowNotification(new Model
 218                                 {
 219                                     message = LOADING_OWNED_WEARABLES_ERROR_MESSAGE,
 220                                     type = Type.GENERIC,
 221                                     timer = 10f,
 222                                     destroyOnFinish = true
 223                                 });
 224
 0225                                 Debug.LogError(error);
 0226                                 loadingWearables = false;
 227                             }
 0228                         });
 0229    }
 230
 231    private void LoadOwnedEmotes()
 232    {
 233        //Only check emotes once every 60 seconds
 43234        if (IsEmotesUpdateInCooldown())
 0235            return;
 236
 43237        lastTimeOwnedEmotesChecked = Time.realtimeSinceStartup;
 238        //TODO only request OwnedEmotes once every minute
 43239        loadEmotesCTS?.Cancel();
 43240        loadEmotesCTS?.Dispose();
 43241        loadEmotesCTS = null;
 242        // we only follow this flow with new profiles
 43243        if (userProfile?.avatar != null)
 244        {
 43245            loadEmotesCTS = new CancellationTokenSource();
 43246            LoadOwnedEmotesTask(loadEmotesCTS.Token);
 247        }
 43248    }
 249
 250    private async UniTaskVoid LoadOwnedEmotesTask(CancellationToken ct = default, int retries = LOADING_OWNED_WEARABLES_
 251    {
 43252        var emotesCatalog = Environment.i.serviceLocator.Get<IEmotesCatalogService>();
 253        try
 254        {
 43255            var embeddedEmotes = Resources.Load<EmbeddedEmotesSO>("EmbeddedEmotes").emotes;
 43256            var emotes = await emotesCatalog.RequestOwnedEmotesAsync(userProfile.userId, ct);
 43257            var emotesList = emotes == null ? embeddedEmotes.Cast<WearableItem>().ToList() : emotes.Concat(embeddedEmote
 43258            var emotesFilter = new HashSet<string>();
 1720259            foreach (var e in emotesList)
 817260                emotesFilter.Add(e.id);
 261
 43262            if(loadingWearables)
 0263                await UniTask.WaitWhile(() => loadingWearables, cancellationToken: ct);
 264
 43265            if (emotesLoadedAsWearables != null)
 266            {
 0267                foreach (var emoteAsWearable in emotesLoadedAsWearables)
 268                {
 0269                    if (emotesFilter.Contains(emoteAsWearable.id))
 270                        continue;
 271
 0272                    emotesList.Add(emoteAsWearable);
 273                }
 274
 0275                emotesLoadedAsWearables = null;
 276            }
 277
 43278            emotesCustomizationDataStore.UnequipMissingEmotes(emotesList);
 43279            emotesCustomizationComponentController.SetEmotes(emotesList.ToArray());
 280
 43281        }
 0282        catch (Exception e)
 283        {
 0284            OperationCanceledException opCanceled = e as OperationCanceledException;
 285            // If the cancellation was requested upwards, dont retry
 0286            if (opCanceled != null && ct.IsCancellationRequested)
 0287                return;
 288
 0289            if (retries > 0)
 290            {
 0291                LoadOwnedEmotesTask(ct, retries - 1);
 0292            }
 293            else
 294            {
 0295                if (opCanceled == null) // Ignore operation canceled exceptions when logging
 0296                    Debug.LogWarning(e.ToString());
 297                const string ERROR = "There was a problem loading your emotes";
 0298                NotificationsController.i.ShowNotification(new Model
 299                {
 300                    message = ERROR,
 301                    type = Type.GENERIC,
 302                    timer = 10f,
 303                    destroyOnFinish = true
 304                });
 305            }
 0306        }
 43307    }
 308
 309    private void QueryNftCollections(string userId)
 310    {
 43311        if (string.IsNullOrEmpty(userId))
 43312            return;
 313
 0314        Environment.i.platform.serviceProviders.theGraph.QueryNftCollections(userProfile.userId, NftCollectionsLayer.ETH
 0315           .Then((nfts) => ownedNftCollectionsL1 = nfts)
 0316           .Catch((error) => Debug.LogError(error));
 317
 0318        Environment.i.platform.serviceProviders.theGraph.QueryNftCollections(userProfile.userId, NftCollectionsLayer.MAT
 0319           .Then((nfts) => ownedNftCollectionsL2 = nfts)
 0320           .Catch((error) => Debug.LogError(error));
 0321    }
 322
 323    public void ReloadOwnedWearablesOnRefocus()
 324    {
 0325        ownedWearablesRemainingRequests = LOADING_OWNED_WEARABLES_RETRIES;
 0326        LoadOwnedWereables(userProfile);
 0327        LoadOwnedEmotes();
 0328    }
 329
 330    private void PlayerRendererLoaded(bool current, bool previous)
 331    {
 15332        if (!current)
 0333            return;
 334
 15335        if (!ownedWearablesAlreadyLoaded)
 336        {
 15337            List<string> equippedOwnedWearables = new List<string>();
 78338            for (int i = 0; i < userProfile.avatar.wearables.Count; i++)
 339            {
 24340                if (catalog.TryGetValue(userProfile.avatar.wearables[i], out WearableItem wearable) &&
 341                    !wearable.data.tags.Contains(WearableLiterals.Tags.BASE_WEARABLE))
 342                {
 0343                    equippedOwnedWearables.Add(userProfile.avatar.wearables[i]);
 344                }
 345            }
 346
 15347            userProfile.SetInventory(equippedOwnedWearables.ToArray());
 348        }
 349
 15350        LoadUserProfile(userProfile, true);
 15351        DataStore.i.common.isPlayerRendererLoaded.OnChange -= PlayerRendererLoaded;
 15352    }
 353
 354    public void LoadUserProfile(UserProfile userProfile, bool forceLoading)
 355    {
 106356        bool avatarEditorNotVisible = renderingEnabled && !view.isOpen;
 106357        bool isPlaying = !Application.isBatchMode;
 358
 106359        if (!forceLoading)
 360        {
 43361            if (isPlaying && avatarEditorNotVisible)
 0362                return;
 363        }
 364
 106365        if (userProfile == null)
 0366            return;
 367
 106368        if (userProfile.avatar == null || string.IsNullOrEmpty(userProfile.avatar.bodyShape))
 3369            return;
 370
 103371        view.InitializeNavigationEvents(!userProfile.hasConnectedWeb3);
 372
 103373        CatalogController.wearableCatalog.TryGetValue(userProfile.avatar.bodyShape, out var bodyShape);
 374
 103375        if (bodyShape == null)
 376        {
 0377            return;
 378        }
 379
 103380        view.SetIsWeb3(userProfile.hasConnectedWeb3);
 381
 103382        ProcessCatalog(this.catalog);
 383
 103384        if (avatarIsDirty)
 1385            return;
 386
 102387        EquipBodyShape(bodyShape);
 102388        EquipSkinColor(userProfile.avatar.skinColor);
 102389        EquipHairColor(userProfile.avatar.hairColor);
 102390        EquipEyesColor(userProfile.avatar.eyeColor);
 391
 102392        model.wearables.Clear();
 102393        view.UnselectAllWearables();
 394
 102395        int wearablesCount = userProfile.avatar.wearables.Count;
 396
 102397        if (isPlayerRendererLoaded)
 398        {
 160399            for (var i = 0; i < wearablesCount; i++)
 400            {
 46401                CatalogController.wearableCatalog.TryGetValue(userProfile.avatar.wearables[i], out var wearable);
 46402                if (wearable == null)
 403                {
 0404                    Debug.LogError($"Couldn't find wearable with ID {userProfile.avatar.wearables[i]}");
 0405                    continue;
 406                }
 407
 46408                if (wearable.IsEmote())
 0409                    EquipEmote(wearable);
 410                else
 46411                    EquipWearable(wearable);
 412            }
 413        }
 414
 102415        EnsureWearablesCategoriesNotEmpty();
 416
 102417        UpdateAvatarPreview(true);
 102418        isAvatarPreviewReady = true;
 102419    }
 420
 421    private void EnsureWearablesCategoriesNotEmpty()
 422    {
 102423        var categoriesInUse = model.wearables
 46424            .Where(x => !x.IsEmote())
 46425            .Select(x => x.data.category).ToArray();
 426
 1632427        for (var i = 0; i < categoriesThatMustHaveSelection.Length; i++)
 428        {
 714429            var category = categoriesThatMustHaveSelection[i];
 714430            if (category != Categories.BODY_SHAPE && !(categoriesInUse.Contains(category)))
 431            {
 432                WearableItem wearable;
 576433                var defaultItemId = WearableLiterals.DefaultWearables.GetDefaultWearable(model.bodyShape.id, category);
 576434                if (defaultItemId != null)
 435                {
 576436                    CatalogController.wearableCatalog.TryGetValue(defaultItemId, out wearable);
 576437                }
 438                else
 439                {
 0440                    wearable = wearablesByCategory[category].FirstOrDefault(x => x.SupportsBodyShape(model.bodyShape.id)
 441                }
 442
 576443                if (wearable != null)
 444                {
 576445                    EquipWearable(wearable);
 446                }
 447            }
 448        }
 102449    }
 450
 451    public void WearableClicked(string wearableId)
 452    {
 20453        CatalogController.wearableCatalog.TryGetValue(wearableId, out var wearable);
 23454        if (wearable == null) return;
 455
 17456        if (wearable.data.category == Categories.BODY_SHAPE)
 457        {
 3458            if (wearable.id == model.bodyShape.id)
 0459                return;
 3460            EquipBodyShape(wearable);
 3461        }
 462        else
 463        {
 14464            if (model.wearables.Contains(wearable))
 465            {
 2466                if (!categoriesThatMustHaveSelection.Contains(wearable.data.category))
 467                {
 0468                    UnequipWearable(wearable);
 0469                }
 470                else
 471                {
 2472                    return;
 473                }
 474            }
 475            else
 476            {
 12477                if (IsTryingToReplaceSkin(wearable))
 0478                    UnequipWearable(model.GetWearable(Categories.SKIN));
 479
 12480                var sameCategoryEquipped = model.GetWearable(wearable.data.category);
 12481                if (sameCategoryEquipped != null)
 2482                    UnequipWearable(sameCategoryEquipped);
 483
 12484                EquipWearable(wearable);
 485            }
 486        }
 487
 15488        UpdateAvatarPreview(false);
 15489        avatarIsDirty = true;
 15490    }
 491
 492    public void HairColorClicked(Color color)
 493    {
 2494        EquipHairColor(color);
 2495        view.SelectHairColor(model.hairColor);
 2496        UpdateAvatarPreview(true);
 2497    }
 498
 499    public void SkinColorClicked(Color color)
 500    {
 2501        EquipSkinColor(color);
 2502        view.SelectSkinColor(model.skinColor);
 2503        UpdateAvatarPreview(true);
 2504    }
 505
 506    public void EyesColorClicked(Color color)
 507    {
 2508        EquipEyesColor(color);
 2509        view.SelectEyeColor(model.eyesColor);
 2510        UpdateAvatarPreview(true);
 2511    }
 512
 513    protected virtual void UpdateAvatarPreview(bool skipAudio)
 514    {
 124515        if (bypassUpdateAvatarPreview)
 0516            return;
 517
 124518        AvatarModel modelToUpdate = model.ToAvatarModel();
 519
 520        // We always keep the loaded emotes into the Avatar Preview
 4960521        foreach (string emoteId in emotesCustomizationDataStore.currentLoadedEmotes.Get())
 522        {
 2356523            modelToUpdate.emotes.Add(new AvatarModel.AvatarEmoteEntry() { urn = emoteId });
 524        }
 525
 124526        view.UpdateAvatarPreview(modelToUpdate, skipAudio);
 124527    }
 528
 529    private void EquipHairColor(Color color)
 530    {
 105531        model.hairColor = color;
 105532        view.SelectHairColor(model.hairColor);
 105533    }
 534
 535    private void EquipEyesColor(Color color)
 536    {
 105537        model.eyesColor = color;
 105538        view.SelectEyeColor(model.eyesColor);
 105539    }
 540
 541    private void EquipSkinColor(Color color)
 542    {
 104543        model.skinColor = color;
 104544        view.SelectSkinColor(model.skinColor);
 104545    }
 546
 547    private void EquipBodyShape(WearableItem bodyShape)
 548    {
 105549        if (bodyShape.data.category != Categories.BODY_SHAPE)
 550        {
 0551            Debug.LogError($"Item ({bodyShape.id} is not a body shape");
 0552            return;
 553        }
 554
 105555        if (model.bodyShape == bodyShape)
 43556            return;
 557
 62558        model.bodyShape = bodyShape;
 62559        emotesCustomizationComponentController.SetEquippedBodyShape(bodyShape.id);
 62560        view.UpdateSelectedBody(bodyShape);
 561
 62562        int wearablesCount = model.wearables.Count;
 182563        for (var i = wearablesCount - 1; i >= 0; i--)
 564        {
 29565            UnequipWearable(model.wearables[i]);
 566        }
 567
 62568        var defaultWearables = WearableLiterals.DefaultWearables.GetDefaultWearables(bodyShape.id);
 1022569        for (var i = 0; i < defaultWearables.Length; i++)
 570        {
 449571            if (catalog.TryGetValue(defaultWearables[i], out var wearable))
 449572                EquipWearable(wearable);
 573        }
 62574    }
 575
 576    private void EquipWearable(WearableItem wearable)
 577    {
 1090578        if (wearable.IsEmote())
 0579            return;
 580
 1090581        if (!wearablesByCategory.ContainsKey(wearable.data.category))
 0582            return;
 583
 1090584        if (wearablesByCategory[wearable.data.category].Contains(wearable) && wearable.SupportsBodyShape(model.bodyShape
 585        {
 1090586            var toReplace = GetWearablesReplacedBy(wearable);
 1090587            toReplace.ForEach(UnequipWearable);
 1090588            model.wearables.Add(wearable);
 1090589            view.EquipWearable(wearable);
 590        }
 1090591    }
 592
 593    private void UnequipWearable(WearableItem wearable)
 594    {
 33595        if (wearable.IsEmote())
 0596            return;
 597
 33598        if (model.wearables.Contains(wearable))
 599        {
 33600            model.wearables.Remove(wearable);
 33601            view.UnequipWearable(wearable);
 33602            avatarIsDirty = true;
 603        }
 33604    }
 605
 606    private void EquipEmote(WearableItem emote)
 607    {
 0608        if (!emote.IsEmote())
 0609            return;
 0610        avatarIsDirty = true;
 0611    }
 612
 613    private void UnequipEmote(WearableItem emote)
 614    {
 0615        if (!emote.IsEmote())
 0616            return;
 617
 0618        avatarIsDirty = true;
 0619    }
 620
 621    public void UnequipAllWearables()
 622    {
 1012623        foreach (var wearable in model.wearables)
 624        {
 431625            if (!wearable.IsEmote())
 431626                view.UnequipWearable(wearable);
 627        }
 628
 75629        model.wearables.Clear();
 75630    }
 631
 632    private void ProcessCatalog(BaseDictionary<string, WearableItem> catalog)
 633    {
 151634        wearablesByCategory.Clear();
 151635        view.RemoveAllWearables();
 151636        bool hasSkin = false;
 151637        bool hasCollectible = false;
 151638        using (var iterator = catalog.Get().GetEnumerator())
 639        {
 5551640            while (iterator.MoveNext())
 641            {
 5400642                if (iterator.Current.Value.IsEmote())
 643                    continue;
 644
 5400645                if (iterator.Current.Value.IsFromThirdPartyCollection
 646                    && !thirdPartyCollectionsActive.Contains(iterator.Current.Value.ThirdPartyCollectionId))
 647                    continue;
 648
 5400649                AddWearable(iterator.Current.Key, iterator.Current.Value);
 5400650                hasSkin = iterator.Current.Value.IsSkin() || hasSkin;
 5400651                hasCollectible = iterator.Current.Value.IsCollectible() || hasCollectible;
 652            }
 151653        }
 151654        view.ShowSkinPopulatedList(hasSkin);
 151655        view.ShowCollectiblesPopulatedList(hasCollectible);
 151656        view.RefreshSelectorsSize();
 151657    }
 658
 659    private void AddWearable(string id, WearableItem wearable)
 660    {
 5411661        if (!wearable.data.tags.Contains(WearableLiterals.Tags.BASE_WEARABLE) && userProfile.GetItemAmount(id) == 0)
 1190662            return;
 663
 4221664        if (!wearablesByCategory.ContainsKey(wearable.data.category))
 1650665            wearablesByCategory.Add(wearable.data.category, new List<WearableItem>());
 666
 4221667        wearablesByCategory[wearable.data.category].Add(wearable);
 4221668        view.AddWearable(wearable, userProfile.GetItemAmount(id),
 669            ShouldShowHideOtherWearablesToast,
 670            ShouldShowReplaceOtherWearablesToast,
 671            ShouldShowIncompatibleWearableToast);
 4221672    }
 673
 674    private void RemoveWearable(string id, WearableItem wearable)
 675    {
 0676        if (wearablesByCategory.ContainsKey(wearable.data.category))
 677        {
 0678            if (wearablesByCategory[wearable.data.category].Remove(wearable))
 679            {
 0680                if (wearablesByCategory[wearable.data.category].Count == 0)
 681                {
 0682                    wearablesByCategory.Remove(wearable.data.category);
 683                }
 684            }
 685        }
 686
 0687        view.RemoveWearable(wearable);
 0688    }
 689
 690    public void RandomizeWearables()
 691    {
 1692        EquipHairColor(view.GetRandomColor());
 1693        EquipEyesColor(view.GetRandomColor());
 694
 7695        List<WearableItem> wearablesToRemove = model.wearables.Where(x => !x.IsEmote()).ToList();
 14696        foreach (var wearable in wearablesToRemove)
 697        {
 6698            model.wearables.Remove(wearable);
 699        }
 700
 1701        view.UnselectAllWearables();
 1702        using (var iterator = wearablesByCategory.GetEnumerator())
 703        {
 12704            while (iterator.MoveNext())
 705            {
 11706                string category = iterator.Current.Key;
 11707                if (!categoriesToRandomize.Contains(category))
 708                {
 709                    continue;
 710                }
 711
 29712                var supportedWearables = iterator.Current.Value.Where(x => x.SupportsBodyShape(model.bodyShape.id)).ToAr
 7713                if (supportedWearables.Length == 0)
 714                {
 0715                    Debug.LogError($"Couldn't get any wearable for category {category} and bodyshape {model.bodyShape.id
 716                }
 717
 7718                var wearable = supportedWearables[Random.Range(0, supportedWearables.Length - 1)];
 7719                EquipWearable(wearable);
 720            }
 1721        }
 722
 1723        UpdateAvatarPreview(false);
 1724    }
 725
 726    private List<WearableItem> GetWearablesReplacedBy(WearableItem wearableItem)
 727    {
 1090728        var wearablesToReplace = new List<WearableItem>();
 1090729        var categoriesToReplace = new HashSet<string>(wearableItem.GetReplacesList(model.bodyShape.id) ?? new string[0])
 730
 1090731        int wearableCount = model.wearables.Count;
 8378732        for (int i = 0; i < wearableCount; i++)
 733        {
 3099734            var wearable = model.wearables[i];
 3099735            if (wearable == null) continue;
 736
 3099737            if (categoriesToReplace.Contains(wearable.data.category))
 738            {
 2739                wearablesToReplace.Add(wearable);
 2740            }
 741            else
 742            {
 743                //For retrocompatibility's sake we check current wearables against new one (compatibility matrix is symm
 3097744                HashSet<string> replacesList = new HashSet<string>(wearable.GetReplacesList(model.bodyShape.id) ?? new s
 3097745                if (replacesList.Contains(wearableItem.data.category))
 746                {
 0747                    wearablesToReplace.Add(wearable);
 748                }
 749            }
 750        }
 751
 1090752        return wearablesToReplace;
 753    }
 754
 88755    public void SetVisibility(bool visible) { avatarEditorVisible.Set(visible); }
 756
 184757    private void OnAvatarEditorVisibleChanged(bool current, bool previous) { SetVisibility_Internal(current); }
 758
 759    private void SetVisibility_Internal(bool visible)
 760    {
 92761        bool isSignUpFlow = DataStore.i.common.isSignUpFlow.Get();
 92762        if (!visible && view.isOpen)
 763        {
 1764            view.ResetPreviewEmote();
 765
 1766            if (isSignUpFlow)
 0767                DataStore.i.virtualAudioMixer.sceneSFXVolume.Set(1f);
 768
 1769            Environment.i.messaging.manager.paused = false;
 1770            DataStore.i.skyboxConfig.avatarMatProfile.Set(AvatarMaterialProfile.InWorld);
 1771            if (prevMouseLockState && isSignUpFlow)
 772            {
 0773                Utils.LockCursor();
 774            }
 775
 776            // NOTE(Brian): SSAO doesn't work correctly with the offseted avatar preview if the renderScale != 1.0
 1777            var asset = GraphicsSettings.renderPipelineAsset as UniversalRenderPipelineAsset;
 1778            asset.renderScale = prevRenderScale;
 779
 1780            if (isSignUpFlow)
 0781                CommonScriptableObjects.isFullscreenHUDOpen.Set(false);
 782
 1783            DataStore.i.common.isPlayerRendererLoaded.OnChange -= PlayerRendererLoaded;
 784
 1785            OnClose?.Invoke();
 0786        }
 91787        else if (visible && !view.isOpen)
 788        {
 43789            if (isSignUpFlow)
 790            {
 0791                DataStore.i.virtualAudioMixer.sceneSFXVolume.Set(0f);
 0792                view.sectionSelector.Hide(true);
 0793            }
 794            else
 795            {
 43796                view.sectionSelector.Show(true);
 797            }
 798
 43799            LoadOwnedWereables(userProfile);
 43800            if (!isSignUpFlow)
 43801                LoadOwnedEmotes();
 802
 43803            LoadCollections();
 43804            Environment.i.messaging.manager.paused = isSignUpFlow;
 43805            DataStore.i.skyboxConfig.avatarMatProfile.Set(AvatarMaterialProfile.InEditor);
 806
 43807            prevMouseLockState = Utils.IsCursorLocked;
 808
 43809            if (isSignUpFlow || !DataStore.i.exploreV2.isInitialized.Get())
 43810                Utils.UnlockCursor();
 811
 812            // NOTE(Brian): SSAO doesn't work correctly with the offseted avatar preview if the renderScale != 1.0
 43813            var asset = GraphicsSettings.renderPipelineAsset as UniversalRenderPipelineAsset;
 43814            prevRenderScale = asset.renderScale;
 43815            asset.renderScale = 1.0f;
 816
 43817            if (isSignUpFlow)
 0818                CommonScriptableObjects.isFullscreenHUDOpen.Set(true);
 819
 43820            DataStore.i.common.isPlayerRendererLoaded.OnChange += PlayerRendererLoaded;
 821
 43822            OnOpen?.Invoke();
 823        }
 824
 92825        view.SetVisibility(visible);
 92826    }
 827
 828    public void Dispose()
 829    {
 43830        loadEmotesCTS?.Cancel();
 43831        loadEmotesCTS?.Dispose();
 43832        loadEmotesCTS = null;
 833
 43834        avatarEditorVisible.OnChange -= OnAvatarEditorVisibleChanged;
 43835        configureBackpackInFullscreenMenu.OnChange -= ConfigureBackpackInFullscreenMenuChanged;
 43836        DataStore.i.common.isPlayerRendererLoaded.OnChange -= PlayerRendererLoaded;
 43837        exploreV2IsOpen.OnChange -= ExploreV2IsOpenChanged;
 43838        emotesCustomizationDataStore.isEmotesCustomizationSelected.OnChange -= HandleEmotesCostumizationSelection;
 43839        emotesCustomizationDataStore.currentLoadedEmotes.OnAdded -= OnNewEmoteAdded;
 840
 43841        emotesCustomizationComponentController.onEmotePreviewed -= OnPreviewEmote;
 43842        emotesCustomizationComponentController.onEmoteEquipped -= OnEmoteEquipped;
 43843        emotesCustomizationComponentController.onEmoteUnequipped -= OnEmoteUnequipped;
 43844        emotesCustomizationComponentController.onEmoteSell -= OnRedirectToEmoteSelling;
 845
 43846        Environment.i.serviceLocator.Get<IApplicationFocusService>().OnApplicationFocus -= OnApplicationFocus;
 847
 43848        CleanUp();
 43849    }
 850
 851    public void CleanUp()
 852    {
 48853        UnequipAllWearables();
 854
 48855        if (view != null)
 48856            view.CleanUp();
 857
 48858        this.userProfile.OnUpdate -= LoadUserProfile;
 48859        this.catalog.OnAdded -= AddWearable;
 48860        this.catalog.OnRemoved -= RemoveWearable;
 48861        DataStore.i.common.isPlayerRendererLoaded.OnChange -= PlayerRendererLoaded;
 48862    }
 863
 0864    public void SetConfiguration(HUDConfiguration configuration) { SetVisibility(configuration.active); }
 865
 866    public void SaveAvatar(Texture2D face256Snapshot, Texture2D bodySnapshot)
 867    {
 1868        var avatarModel = model.ToAvatarModel();
 869
 870        // Add the equipped emotes to the avatar model
 1871        List<AvatarModel.AvatarEmoteEntry> emoteEntries = new List<AvatarModel.AvatarEmoteEntry>();
 1872        int equippedEmotesCount = emotesCustomizationDataStore.unsavedEquippedEmotes.Count();
 22873        for (int i = 0; i < equippedEmotesCount; i++)
 874        {
 10875            var equippedEmote = emotesCustomizationDataStore.unsavedEquippedEmotes[i];
 10876            if (equippedEmote == null)
 877                continue;
 0878            emoteEntries.Add(new AvatarModel.AvatarEmoteEntry { slot = i, urn = equippedEmote.id });
 879        }
 880
 1881        avatarModel.emotes = emoteEntries;
 882
 1883        SendNewEquippedWearablesAnalytics(userProfile.avatar.wearables, avatarModel.wearables);
 1884        emotesCustomizationDataStore.equippedEmotes.Set(emotesCustomizationDataStore.unsavedEquippedEmotes.Get());
 885
 886
 1887        WebInterface.SendSaveAvatar(avatarModel, face256Snapshot, bodySnapshot, DataStore.i.common.isSignUpFlow.Get());
 1888        userProfile.OverrideAvatar(avatarModel, face256Snapshot);
 889
 1890        if (DataStore.i.common.isSignUpFlow.Get())
 891        {
 0892            DataStore.i.HUDs.signupVisible.Set(true);
 0893            newUserExperienceAnalytics.AvatarEditSuccessNux();
 894        }
 895
 896
 1897        avatarIsDirty = false;
 1898        SetVisibility(false);
 1899    }
 900
 901    public void GoToMarketplaceOrConnectWallet()
 902    {
 0903        if (userProfile.hasConnectedWeb3)
 0904            WebInterface.OpenURL(URL_MARKET_PLACE);
 905        else
 0906            WebInterface.OpenURL(URL_GET_A_WALLET);
 0907    }
 908
 909    public void SellCollectible(string collectibleId)
 910    {
 0911        var ownedCollectible = ownedNftCollectionsL1.FirstOrDefault(nft => nft.urn == collectibleId);
 0912        if (ownedCollectible == null)
 0913            ownedCollectible = ownedNftCollectionsL2.FirstOrDefault(nft => nft.urn == collectibleId);
 914
 0915        if (ownedCollectible != null)
 0916            WebInterface.OpenURL(URL_SELL_SPECIFIC_COLLECTIBLE.Replace("{collectionId}", ownedCollectible.collectionId).
 917        else
 0918            WebInterface.OpenURL(URL_SELL_COLLECTIBLE_GENERIC);
 0919    }
 920
 0921    public void ToggleVisibility() { SetVisibility(!view.isOpen); }
 922
 96923    private void ConfigureBackpackInFullscreenMenuChanged(Transform currentParentTransform, Transform previousParentTran
 924
 925    private void ExploreV2IsOpenChanged(bool current, bool previous)
 926    {
 0927        if (!current && avatarIsDirty)
 928        {
 0929            avatarIsDirty = false;
 930
 0931            LoadUserProfile(userProfile, true);
 932
 0933            emotesCustomizationComponentController.RestoreEmoteSlots();
 934        }
 0935    }
 936
 937    private void LoadCollections()
 938    {
 43939        if (!isThirdPartyCollectionsEnabled || collectionsAlreadyLoaded)
 43940            return;
 941
 0942        WearablesFetchingHelper.GetThirdPartyCollections()
 943            .Then((collections) =>
 944            {
 0945                view.LoadCollectionsDropdown(collections);
 0946                collectionsAlreadyLoaded = true;
 0947                LoadUserThirdPartyWearables();
 0948            })
 0949            .Catch((error) => Debug.LogError(error));
 0950    }
 951
 952    private void LoadUserThirdPartyWearables()
 953    {
 0954        List<string> collectionIdsToLoad = new List<string>();
 0955        foreach (string wearableId in userProfile.avatar.wearables)
 956        {
 0957            CatalogController.wearableCatalog.TryGetValue(wearableId, out var wearable);
 958
 0959            if (wearable != null && wearable.IsFromThirdPartyCollection)
 960            {
 0961                if (!collectionIdsToLoad.Contains(wearable.ThirdPartyCollectionId))
 0962                    collectionIdsToLoad.Add(wearable.ThirdPartyCollectionId);
 963            }
 964        }
 965
 0966        foreach (string collectionId in collectionIdsToLoad)
 967        {
 0968            view.ToggleThirdPartyCollection(collectionId, true);
 969        }
 0970    }
 971
 972    public void ToggleThirdPartyCollection(bool isOn, string collectionId, string collectionName)
 973    {
 1974        if (isOn)
 1975            FetchAndShowThirdPartyCollection(collectionId, collectionName);
 976        else
 0977            RemoveThirdPartyCollection(collectionId);
 0978    }
 979
 980    private void FetchAndShowThirdPartyCollection(string collectionId, string collectionName)
 981    {
 1982        view.BlockCollectionsDropdown(true);
 1983        CatalogController.RequestThirdPartyWearablesByCollection(userProfile.userId, collectionId)
 984            .Then(wearables =>
 985            {
 0986                wearables = wearables.Where(x => x.ThirdPartyCollectionId == collectionId).ToArray();
 0987                if (wearables.Count().Equals(0)) view.ShowNoItemOfWearableCollectionWarning();
 0988                thirdPartyCollectionsActive.Add(collectionId);
 0989                foreach (var wearable in wearables)
 990                {
 0991                    if (!userProfile.ContainsInInventory(wearable.id))
 992                    {
 0993                        userProfile.AddToInventory(wearable.id);
 994
 0995                        if (!thirdPartyWearablesLoaded.Contains(wearable.id))
 0996                            thirdPartyWearablesLoaded.Add(wearable.id);
 997                    }
 998                }
 999
 01000                view.BlockCollectionsDropdown(false);
 01001                LoadUserProfile(userProfile, true);
 01002                view.RefreshSelectorsSize();
 01003            })
 1004            .Catch((error) =>
 1005            {
 01006                view.BlockCollectionsDropdown(false);
 01007                Debug.LogError(error);
 01008            });
 11009    }
 1010
 1011    private void RemoveThirdPartyCollection(string collectionId)
 1012    {
 01013        var wearablesToRemove = CatalogController.i.Wearables.GetValues()
 01014            .Where(wearable => !userProfile.HasEquipped(wearable.id)
 1015                               && wearable.ThirdPartyCollectionId == collectionId)
 01016            .Select(item => item.id)
 1017            .ToList();
 01018        CatalogController.i.Remove(wearablesToRemove);
 01019        thirdPartyCollectionsActive.Remove(collectionId);
 1020
 01021        foreach (string wearableId in wearablesToRemove)
 1022        {
 01023            userProfile.RemoveFromInventory(wearableId);
 01024            thirdPartyWearablesLoaded.Remove(wearableId);
 1025        }
 1026
 01027        LoadUserProfile(userProfile, true);
 01028    }
 1029
 1030    private bool ShouldShowHideOtherWearablesToast(WearableItem wearable)
 1031    {
 01032        var isWearingSkinAlready = model.wearables.Any(item => item.IsSkin());
 01033        return wearable.IsSkin() && !isWearingSkinAlready;
 1034    }
 1035
 1036    private bool ShouldShowIncompatibleWearableToast(WearableItem wearable)
 1037    {
 01038        if(wearable.data.category == WearableLiterals.Categories.BODY_SHAPE || wearable.data.category == WearableLiteral
 01039            return false;
 1040        else
 01041            return !wearable.SupportsBodyShape(model.bodyShape.id);
 1042    }
 1043
 1044    private bool IsTryingToReplaceSkin(WearableItem wearable)
 1045    {
 121046        return model.wearables.Any(skin =>
 1047        {
 801048            return skin.IsSkin()
 1049                   && skin.DoesHide(wearable.data.category, model.bodyShape.id);
 1050        });
 1051    }
 1052
 1053    private bool ShouldShowReplaceOtherWearablesToast(WearableItem wearable)
 1054    {
 01055        if (IsTryingToReplaceSkin(wearable)) return true;
 01056        var toReplace = GetWearablesReplacedBy(wearable);
 01057        if (wearable == null || toReplace.Count == 0) return false;
 01058        if (model.wearables.Contains(wearable)) return false;
 1059
 1060        // NOTE: why just 1?
 01061        if (toReplace.Count == 1)
 1062        {
 01063            var w = toReplace[0];
 01064            if (w.data.category == wearable.data.category)
 01065                return false;
 1066        }
 01067        return true;
 1068    }
 1069
 1070
 1071    private void HandleEmotesCostumizationSelection(bool current, bool previous)
 1072    {
 01073        if (!current)
 01074            return;
 1075
 01076        view.sectionSelector.GetSection(AvatarEditorHUDView.EMOTES_SECTION_INDEX).SelectToggle();
 01077    }
 1078
 1079    private void OnNewEmoteAdded(string emoteId)
 1080    {
 01081        if (!isAvatarPreviewReady)
 01082            return;
 1083
 01084        UpdateAvatarPreview(true);
 01085    }
 1086
 01087    private void OnPreviewEmote(string emoteId) { view.PlayPreviewEmote(emoteId); }
 1088
 1089    private void OnEmoteEquipped(string emoteId)
 1090    {
 01091        catalog.TryGetValue(emoteId, out WearableItem equippedEmote);
 1092
 01093        if (equippedEmote != null)
 01094            EquipEmote(equippedEmote);
 01095    }
 1096
 1097    private void OnEmoteUnequipped(string emoteId)
 1098    {
 01099        catalog.TryGetValue(emoteId, out WearableItem unequippedEmote);
 1100
 01101        if (unequippedEmote != null)
 01102            UnequipEmote(unequippedEmote);
 01103    }
 1104
 01105    private void OnRedirectToEmoteSelling(string emoteId) { SellCollectible(emoteId); }
 1106
 1107    internal void SendNewEquippedWearablesAnalytics(List<string> oldWearables, List<string> newWearables)
 1108    {
 281109        for (int i = 0; i < newWearables.Count; i++)
 1110        {
 121111            if (oldWearables.Contains(newWearables[i]))
 1112                continue;
 1113
 91114            catalog.TryGetValue(newWearables[i], out WearableItem newEquippedEmote);
 91115            if (newEquippedEmote != null && !newEquippedEmote.IsEmote())
 91116                SendEquipWearableAnalytic(newEquippedEmote);
 1117        }
 21118    }
 1119
 1120    private void SendEquipWearableAnalytic(WearableItem equippedWearable)
 1121    {
 91122        Dictionary<string, string> data = new Dictionary<string, string>();
 91123        data.Add("name", equippedWearable.GetName());
 91124        data.Add("rarity", equippedWearable.rarity);
 91125        data.Add("category", equippedWearable.data.category);
 91126        data.Add("linked_wearable", equippedWearable.IsFromThirdPartyCollection.ToString());
 91127        data.Add("third_party_collection_id", equippedWearable.ThirdPartyCollectionId);
 91128        data.Add("is_in_l2", equippedWearable.IsInL2().ToString());
 91129        data.Add("smart_item", equippedWearable.IsSmart().ToString());
 1130
 91131        analytics.SendAnalytic(EQUIP_WEARABLE_METRIC, data);
 91132    }
 1133
 481134    internal virtual IEmotesCustomizationComponentController CreateEmotesController() => new EmotesCustomizationComponen
 1135
 1136    private bool IsWearableUpdateInCooldown()
 1137    {
 01138        return Time.realtimeSinceStartup < lastTimeOwnedWearablesChecked + ownedWearableEmotesRequestRetryTime;
 1139    }
 1140
 1141    private bool IsEmotesUpdateInCooldown()
 1142    {
 431143        return Time.realtimeSinceStartup < lastTimeOwnedEmotesChecked + ownedWearableEmotesRequestRetryTime;
 1144    }
 1145
 1146    private bool AreWearablesAlreadyLoaded()
 1147    {
 01148        return ownedWearablesAlreadyLoaded || ownedWearablesRemainingRequests <= 0;
 1149    }
 1150
 1151    private void OnApplicationFocus()
 1152    {
 01153        lastTimeOwnedWearablesChecked = -ownedWearableEmotesRequestRetryTime;
 01154        lastTimeOwnedEmotesChecked = -ownedWearableEmotesRequestRetryTime;
 01155    }
 1156}

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(System.Boolean)
EquipHairColor(UnityEngine.Color)
EquipEyesColor(UnityEngine.Color)
EquipSkinColor(UnityEngine.Color)
EquipBodyShape(WearableItem)
EquipWearable(WearableItem)
UnequipWearable(WearableItem)
EquipEmote(WearableItem)
UnequipEmote(WearableItem)
UnequipAllWearables()
ProcessCatalog(.BaseDictionary[String,WearableItem])
AddWearable(System.String, WearableItem)
RemoveWearable(System.String, WearableItem)
RandomizeWearables()
GetWearablesReplacedBy(WearableItem)
SetVisibility(System.Boolean)
OnAvatarEditorVisibleChanged(System.Boolean, System.Boolean)
SetVisibility_Internal(System.Boolean)
Dispose()
CleanUp()
SetConfiguration(HUDConfiguration)
SaveAvatar(UnityEngine.Texture2D, UnityEngine.Texture2D)
GoToMarketplaceOrConnectWallet()
SellCollectible(System.String)
ToggleVisibility()
ConfigureBackpackInFullscreenMenuChanged(UnityEngine.Transform, UnityEngine.Transform)
ExploreV2IsOpenChanged(System.Boolean, System.Boolean)
LoadCollections()
LoadUserThirdPartyWearables()
ToggleThirdPartyCollection(System.Boolean, System.String, System.String)
FetchAndShowThirdPartyCollection(System.String, System.String)
RemoveThirdPartyCollection(System.String)
ShouldShowHideOtherWearablesToast(WearableItem)
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()