< 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:0
Uncovered lines:574
Coverable lines:574
Total lines:1187
Line coverage:0% (0 of 574)
Covered branches:0
Total branches:0
Covered methods:0
Total methods:76
Method coverage:0% (0 of 76)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
AvatarEditorHUDController()0%2100%
AvatarEditorHUDController(...)0%2100%
Initialize(...)0%2100%
InitializeAsyncEmoteController()0%12300%
SetCatalog(...)0%6200%
OnAdditionalWearableRemoved(...)0%2100%
OnAdditionalWearableAdded(...)0%2100%
LoadUserProfile(...)0%2100%
>c__DisplayClass72_0/<<LoadOwnedWereables()0%90900%
LoadOwnedWereables(...)0%20400%
LoadOwnedEmotes()0%30500%
LoadOwnedEmotesTask()0%3801900%
QueryNftCollections(...)0%20400%
PlayerRendererLoaded(...)0%42600%
LoadUserProfile(...)0%1821300%
EnsureWearablesCategoriesNotEmpty()0%72800%
WearableClicked(...)0%72800%
HairColorClicked(...)0%2100%
SkinColorClicked(...)0%2100%
EyesColorClicked(...)0%2100%
UpdateAvatarPreview()0%20400%
EquipHairColor(...)0%2100%
EquipEyesColor(...)0%2100%
EquipSkinColor(...)0%2100%
EquipBodyShape(...)0%42600%
EquipWearable(...)0%42600%
UnequipWearable(...)0%12300%
EquipEmote(...)0%6200%
UnequipEmote(...)0%6200%
UnequipAllWearables()0%12300%
ProcessCatalog(...)0%42600%
AddWearable(...)0%20400%
RemoveWearable(...)0%20400%
RandomizeWearables()0%42600%
GetWearablesReplacedBy(...)0%56700%
SetVisibility(...)0%2100%
OnAvatarEditorVisibleChanged(...)0%2100%
SetVisibility_Internal(...)0%2401500%
Dispose()0%2100%
CleanUp()0%6200%
SetConfiguration(...)0%2100%
SaveAvatar(...)0%20400%
GoToMarketplaceOrConnectWallet()0%6200%
SellCollectible(...)0%12300%
ToggleVisibility()0%2100%
ConfigureBackpackInFullscreenMenuChanged(...)0%2100%
ExploreV2IsOpenChanged(...)0%12300%
LoadCollections()0%20400%
LoadUserThirdPartyWearables()0%42600%
ToggleThirdPartyCollection(...)0%6200%
>c__DisplayClass113_0/<<FetchAndShowThirdPartyCollection()0%90900%
FetchAndShowThirdPartyCollection(...)0%2100%
RemoveThirdPartyCollection(...)0%12300%
ShouldShowHideOtherWearablesToast(...)0%12300%
ShouldShowIncompatibleWearableToast(...)0%12300%
IsTryingToReplaceSkin(...)0%2100%
ShouldShowReplaceOtherWearablesToast(...)0%56700%
HandleEmotesCostumizationSelection(...)0%6200%
OnNewEmoteAdded(...)0%6200%
OnPreviewEmote(...)0%2100%
OnEmoteEquipped(...)0%6200%
OnEmoteUnequipped(...)0%6200%
OnRedirectToEmoteSelling(...)0%2100%
SendNewEquippedWearablesAnalytics(...)0%30500%
SendEquipWearableAnalytic(...)0%2100%
IsWearableUpdateInCooldown()0%2100%
IsEmotesUpdateInCooldown()0%2100%
OnApplicationFocus()0%2100%

File(s)

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

#LineLine coverage
 1using Cysharp.Threading.Tasks;
 2using DCL;
 3using DCL.Emotes;
 4using DCL.EmotesCustomization;
 5using DCL.Helpers;
 6using DCL.Interface;
 7using DCL.NotificationModel;
 8using DCL.Tasks;
 9using DCLServices.WearablesCatalogService;
 10using MainScripts.DCL.Controllers.HUD.CharacterPreview;
 11using System;
 12using System.Collections.Generic;
 13using System.Linq;
 14using System.Threading;
 15using UnityEngine;
 16using UnityEngine.Rendering;
 17using UnityEngine.Rendering.Universal;
 18using Environment = DCL.Environment;
 19using Random = UnityEngine.Random;
 20using Type = DCL.NotificationModel.Type;
 21using Categories = WearableLiterals.Categories;
 22
 23public class AvatarEditorHUDController : IHUD
 24{
 25    private const string LOADING_OWNED_WEARABLES_ERROR_MESSAGE = "There was a problem loading your wearables";
 26    private const string URL_MARKET_PLACE = "https://market.decentraland.org/browse?section=wearables";
 27    private const string URL_GET_A_WALLET = "https://docs.decentraland.org/get-a-wallet";
 28    private const string URL_SELL_COLLECTIBLE_GENERIC = "https://market.decentraland.org/account";
 29    private const string URL_SELL_SPECIFIC_COLLECTIBLE = "https://market.decentraland.org/contracts/{collectionId}/token
 30    private const string THIRD_PARTY_COLLECTIONS_FEATURE_FLAG = "third_party_collections";
 31    private const int REQUESTS_COOLDOWN_TIME = 60;
 32    internal const string EQUIP_WEARABLE_METRIC = "equip_wearable";
 033    protected static readonly string[] categoriesThatMustHaveSelection = { Categories.BODY_SHAPE, Categories.UPPER_BODY,
 034    protected static readonly string[] categoriesToRandomize = { Categories.HAIR, Categories.EYES, Categories.EYEBROWS, 
 35
 36    [NonSerialized]
 37    public bool bypassUpdateAvatarPreview = false;
 38
 039    private UserProfile userProfile => userProfileBridge.GetOwn();
 40    private readonly IAnalytics analytics;
 41    private readonly INewUserExperienceAnalytics newUserExperienceAnalytics;
 42    private BaseDictionary<string, WearableItem> catalog;
 43    private readonly IWearablesCatalogService wearablesCatalogService;
 44    private readonly IUserProfileBridge userProfileBridge;
 045    private bool renderingEnabled => CommonScriptableObjects.rendererState.Get();
 046    private bool isPlayerRendererLoaded => DataStore.i.common.isPlayerRendererLoaded.Get();
 047    private BaseVariable<bool> avatarEditorVisible => DataStore.i.HUDs.avatarEditorVisible;
 048    private BaseVariable<Transform> configureBackpackInFullscreenMenu => DataStore.i.exploreV2.configureBackpackInFullsc
 049    private BaseVariable<bool> exploreV2IsOpen => DataStore.i.exploreV2.isOpen;
 050    private DataStore_EmotesCustomization emotesCustomizationDataStore => DataStore.i.emotesCustomization;
 51
 52    private readonly DataStore_FeatureFlag featureFlags;
 53
 054    private readonly Dictionary<string, List<WearableItem>> wearablesByCategory = new Dictionary<string, List<WearableIt
 055    protected readonly AvatarEditorHUDModel model = new AvatarEditorHUDModel();
 56
 57    private ColorList skinColorList;
 58    private ColorList eyeColorList;
 59    private ColorList hairColorList;
 60    private bool prevMouseLockState = false;
 61    private bool ownedWearablesAlreadyLoaded = false;
 062    private List<Nft> ownedNftCollectionsL1 = new List<Nft>();
 063    private List<Nft> ownedNftCollectionsL2 = new List<Nft>();
 64    internal bool avatarIsDirty = false;
 65    private float lastTimeOwnedWearablesChecked = 0;
 066    private float lastTimeOwnedEmotesChecked = float.MinValue;
 67    internal bool collectionsAlreadyLoaded = false;
 068    private float prevRenderScale = 1.0f;
 69    private bool isAvatarPreviewReady;
 070    private List<string> thirdPartyWearablesLoaded = new List<string>();
 071    private List<string> thirdPartyCollectionsActive = new List<string>();
 072    private CancellationTokenSource loadEmotesCTS = new CancellationTokenSource();
 073    private CancellationTokenSource loadOwnedWearablesCTS = new CancellationTokenSource();
 074    private CancellationTokenSource loadThirdPartyWearablesCTS = new CancellationTokenSource();
 75    private bool loadingWearables;
 76    private WearableItem[] emotesLoadedAsWearables;
 77    private AvatarEditorHUDAnimationController avatarEditorHUDAnimationController;
 78    private IEmotesCustomizationComponentController emotesCustomizationComponentController;
 079    private bool isThirdPartyCollectionsEnabled => featureFlags.flags.Get().IsFeatureEnabled(THIRD_PARTY_COLLECTIONS_FEA
 80    private Service<IEmotesCatalogService> emotesCatalogService;
 81
 82    internal AvatarEditorHUDView view;
 83
 84    public event Action OnOpen;
 85    public event Action OnClose;
 86
 087    public AvatarEditorHUDController(
 88        DataStore_FeatureFlag featureFlags,
 89        IAnalytics analytics,
 90        IWearablesCatalogService wearablesCatalogService,
 91        IUserProfileBridge userProfileBridge)
 92    {
 093        this.featureFlags = featureFlags;
 094        this.analytics = analytics;
 095        this.newUserExperienceAnalytics = new NewUserExperienceAnalytics(analytics);
 096        this.wearablesCatalogService = wearablesCatalogService;
 097        this.userProfileBridge = userProfileBridge;
 098    }
 99
 100    public void Initialize(
 101        bool bypassUpdateAvatarPreview,
 102        IPreviewCameraRotationController previewCameraRotationController)
 103    {
 0104        this.bypassUpdateAvatarPreview = bypassUpdateAvatarPreview;
 105
 0106        view = AvatarEditorHUDView.Create(this, previewCameraRotationController);
 107
 0108        view.skinsFeatureContainer.SetActive(true);
 0109        avatarEditorVisible.OnChange += OnAvatarEditorVisibleChanged;
 0110        OnAvatarEditorVisibleChanged(avatarEditorVisible.Get(), false);
 111
 0112        configureBackpackInFullscreenMenu.OnChange += ConfigureBackpackInFullscreenMenuChanged;
 0113        ConfigureBackpackInFullscreenMenuChanged(configureBackpackInFullscreenMenu.Get(), null);
 114
 0115        exploreV2IsOpen.OnChange += ExploreV2IsOpenChanged;
 116
 0117        skinColorList = Resources.Load<ColorList>("SkinTone");
 0118        hairColorList = Resources.Load<ColorList>("HairColor");
 0119        eyeColorList = Resources.Load<ColorList>("EyeColor");
 0120        view.SetColors(skinColorList.colors, hairColorList.colors, eyeColorList.colors);
 121
 0122        SetCatalog(wearablesCatalogService.WearablesCatalog);
 123
 0124        this.userProfile.OnUpdate += LoadUserProfile;
 125
 0126        view.SetSectionActive(AvatarEditorHUDView.EMOTES_SECTION_INDEX, false);
 127
 0128        InitializeAsyncEmoteController();
 0129    }
 130
 131    private async void InitializeAsyncEmoteController()
 132    {
 0133        EmbeddedEmotesSO embeddedEmotesSo = await emotesCatalogService.Ref.GetEmbeddedEmotes();
 134
 0135        emotesCustomizationComponentController = new EmotesCustomizationComponentController(
 136            DataStore.i.emotesCustomization,
 137            view.CharacterPreview.GetEmotesController(),
 138            DataStore.i.exploreV2,
 139            DataStore.i.HUDs,
 140            view.emotesSection.transform);
 141        //Initialize with embedded emotes
 0142        emotesCustomizationComponentController.SetEmotes(embeddedEmotesSo.GetAllEmotes().ToArray());
 0143        view.SetSectionActive(AvatarEditorHUDView.EMOTES_SECTION_INDEX, true);
 144
 0145        emotesCustomizationDataStore.isEmotesCustomizationSelected.OnChange += HandleEmotesCostumizationSelection;
 0146        emotesCustomizationDataStore.currentLoadedEmotes.OnAdded += OnNewEmoteAdded;
 147
 0148        emotesCustomizationComponentController.onEmotePreviewed += OnPreviewEmote;
 0149        emotesCustomizationComponentController.onEmoteEquipped += OnEmoteEquipped;
 0150        emotesCustomizationComponentController.onEmoteUnequipped += OnEmoteUnequipped;
 0151        emotesCustomizationComponentController.onEmoteSell += OnRedirectToEmoteSelling;
 152
 0153        LoadUserProfile(userProfile, true);
 154
 0155        DataStore.i.HUDs.isAvatarEditorInitialized.Set(true);
 156
 0157        view.SetThirdPartyCollectionsVisibility(isThirdPartyCollectionsEnabled);
 158
 0159        this.avatarEditorHUDAnimationController = new AvatarEditorHUDAnimationController(view, wearablesCatalogService);
 160
 0161        Environment.i.serviceLocator.Get<IApplicationFocusService>().OnApplicationFocus += OnApplicationFocus;
 0162    }
 163
 164    private void SetCatalog(BaseDictionary<string, WearableItem> catalog)
 165    {
 0166        if (this.catalog != null)
 167        {
 0168            this.catalog.OnAdded -= OnAdditionalWearableAdded;
 0169            this.catalog.OnRemoved -= OnAdditionalWearableRemoved;
 170        }
 171
 0172        this.catalog = catalog;
 173
 0174        ProcessCatalog(this.catalog);
 175
 0176        this.catalog.OnAdded += OnAdditionalWearableAdded;
 0177        this.catalog.OnRemoved += OnAdditionalWearableRemoved;
 0178    }
 179
 180    private void OnAdditionalWearableRemoved(string s, WearableItem item)
 181    {
 0182        RemoveWearable(s, item);
 0183        view.RefreshSelectorsSize();
 0184    }
 185
 186    private void OnAdditionalWearableAdded(string id, WearableItem item)
 187    {
 0188        AddWearable(id, item);
 0189        view.RefreshSelectorsSize();
 0190    }
 191
 192    private void LoadUserProfile(UserProfile userProfile)
 193    {
 0194        LoadUserProfile(userProfile, false);
 0195        QueryNftCollections(userProfile.userId);
 0196    }
 197
 198    private void LoadOwnedWereables(UserProfile userProfile)
 199    {
 200        async UniTaskVoid RequestOwnedWearablesAsync(CancellationToken ct)
 201        {
 0202            lastTimeOwnedWearablesChecked = Time.realtimeSinceStartup;
 0203            loadingWearables = true;
 204
 205            try
 206            {
 0207                var ownedWearables = await wearablesCatalogService.RequestOwnedWearablesAsync(
 208                    userProfile.userId,
 209                    1,
 210                    int.MaxValue,
 211                    true,
 212                    ct);
 213
 0214                ownedWearablesAlreadyLoaded = true;
 215                //Prior profile V1 emotes must be retrieved along the wearables, onwards they will be requested separate
 0216                this.userProfile.SetInventory(ownedWearables.wearables.Select(x => x.id).Concat(thirdPartyWearablesLoade
 0217                LoadUserProfile(userProfile, true);
 0218                if (userProfile != null && userProfile.avatar != null)
 0219                    emotesLoadedAsWearables = ownedWearables.wearables.Where(x => x.IsEmote()).ToArray();
 0220            }
 221            catch (Exception e)
 222            {
 0223                LoadUserProfile(userProfile, true);
 224
 0225                NotificationsController.i.ShowNotification(new Model
 226                {
 227                    message = LOADING_OWNED_WEARABLES_ERROR_MESSAGE,
 228                    type = Type.GENERIC,
 229                    timer = 10f,
 230                    destroyOnFinish = true
 231                });
 232
 0233                Debug.LogException(e);
 0234            }
 235            finally
 236            {
 0237                loadingWearables = false;
 238            }
 0239        }
 240
 241        //If there is no userID we dont fetch owned wearabales
 0242        if(string.IsNullOrEmpty(userProfile.userId))
 0243            return;
 244
 245        // If wearables are loaded, we are in wearable cooldown, we dont fetch again owned wearables
 0246        if (ownedWearablesAlreadyLoaded && IsWearableUpdateInCooldown())
 0247            return;
 248
 0249        loadOwnedWearablesCTS = loadOwnedWearablesCTS.SafeRestart();
 0250        RequestOwnedWearablesAsync(loadOwnedWearablesCTS.Token).Forget();
 0251    }
 252
 253    private void LoadOwnedEmotes()
 254    {
 255        //Only check emotes once every 60 seconds
 0256        if (IsEmotesUpdateInCooldown())
 0257            return;
 258
 0259        lastTimeOwnedEmotesChecked = Time.realtimeSinceStartup;
 260        //TODO only request OwnedEmotes once every minute
 261
 0262        loadEmotesCTS.SafeCancelAndDispose();
 263        // we only follow this flow with new profiles
 0264        if (userProfile?.avatar != null)
 265        {
 0266            loadEmotesCTS = loadEmotesCTS.SafeRestart();
 0267            LoadOwnedEmotesTask(loadEmotesCTS.Token);
 268        }
 0269    }
 270
 271    private async UniTaskVoid LoadOwnedEmotesTask(CancellationToken ct = default, int retries = 3)
 272    {
 0273        var emotesCatalog = Environment.i.serviceLocator.Get<IEmotesCatalogService>();
 274        try
 275        {
 0276            EmbeddedEmotesSO embeddedEmoteTask = await emotesCatalogService.Ref.GetEmbeddedEmotes();
 0277            var embeddedEmotes = embeddedEmoteTask.GetAllEmotes();
 0278            var emotes = await emotesCatalog.RequestOwnedEmotesAsync(userProfile.userId, ct);
 0279            var emotesList = emotes == null ? embeddedEmotes.Cast<WearableItem>().ToList() : emotes.Concat(embeddedEmote
 0280            var emotesFilter = new HashSet<string>();
 0281            foreach (var e in emotesList)
 0282                emotesFilter.Add(e.id);
 283
 0284            if (loadingWearables)
 0285                await UniTaskUtils.WaitForBoolean(ref loadingWearables, false, cancellationToken: ct);
 286
 0287            if (emotesLoadedAsWearables != null)
 288            {
 0289                foreach (var emoteAsWearable in emotesLoadedAsWearables)
 290                {
 0291                    if (emotesFilter.Contains(emoteAsWearable.id))
 292                        continue;
 293
 0294                    emotesList.Add(emoteAsWearable);
 295                }
 296
 0297                emotesLoadedAsWearables = null;
 298            }
 299
 0300            emotesCustomizationDataStore.UnequipMissingEmotes(emotesList);
 0301            emotesCustomizationComponentController.SetEmotes(emotesList.ToArray());
 302
 0303        }
 0304        catch (Exception e)
 305        {
 0306            OperationCanceledException opCanceled = e as OperationCanceledException;
 307            // If the cancellation was requested upwards, dont retry
 0308            if (opCanceled != null && ct.IsCancellationRequested)
 0309                return;
 310
 0311            if (retries > 0)
 312            {
 0313                LoadOwnedEmotesTask(ct, retries - 1);
 314            }
 315            else
 316            {
 0317                if (opCanceled == null) // Ignore operation canceled exceptions when logging
 0318                    Debug.LogWarning(e.ToString());
 319                const string ERROR = "There was a problem loading your emotes";
 0320                NotificationsController.i.ShowNotification(new Model
 321                {
 322                    message = ERROR,
 323                    type = Type.GENERIC,
 324                    timer = 10f,
 325                    destroyOnFinish = true
 326                });
 327            }
 0328        }
 0329    }
 330
 331    private void QueryNftCollections(string userId)
 332    {
 0333        if (string.IsNullOrEmpty(userId))
 0334            return;
 335
 0336        Environment.i.platform.serviceProviders.theGraph.QueryNftCollections(userProfile.userId, NftCollectionsLayer.ETH
 0337           .Then((nfts) => ownedNftCollectionsL1 = nfts)
 0338           .Catch((error) => Debug.LogError(error));
 339
 0340        Environment.i.platform.serviceProviders.theGraph.QueryNftCollections(userProfile.userId, NftCollectionsLayer.MAT
 0341           .Then((nfts) => ownedNftCollectionsL2 = nfts)
 0342           .Catch((error) => Debug.LogError(error));
 0343    }
 344
 345    private void PlayerRendererLoaded(bool current, bool previous)
 346    {
 0347        if (!current)
 0348            return;
 349
 0350        if (!ownedWearablesAlreadyLoaded)
 351        {
 0352            List<string> equippedOwnedWearables = new List<string>();
 0353            for (int i = 0; i < userProfile.avatar.wearables.Count; i++)
 354            {
 0355                if (catalog.TryGetValue(userProfile.avatar.wearables[i], out WearableItem wearable) &&
 356                    !wearable.data.tags.Contains(WearableLiterals.Tags.BASE_WEARABLE))
 357                {
 0358                    equippedOwnedWearables.Add(userProfile.avatar.wearables[i]);
 359                }
 360            }
 361
 0362            userProfile.SetInventory(equippedOwnedWearables);
 363        }
 364
 0365        LoadUserProfile(userProfile, true);
 0366        DataStore.i.common.isPlayerRendererLoaded.OnChange -= PlayerRendererLoaded;
 0367    }
 368
 369    private void LoadUserProfile(UserProfile userProfile, bool forceLoading)
 370    {
 0371        bool avatarEditorNotVisible = renderingEnabled && !view.isOpen;
 0372        bool isPlaying = !Application.isBatchMode;
 373
 0374        if (!forceLoading)
 375        {
 0376            if (isPlaying && avatarEditorNotVisible)
 0377                return;
 378        }
 379
 0380        if (userProfile == null)
 0381            return;
 382
 0383        if (userProfile.avatar == null || string.IsNullOrEmpty(userProfile.avatar.bodyShape))
 0384            return;
 385
 0386        view.InitializeNavigationEvents(!userProfile.hasConnectedWeb3);
 387
 0388        wearablesCatalogService.WearablesCatalog.TryGetValue(userProfile.avatar.bodyShape, out var bodyShape);
 389
 0390        if (bodyShape == null)
 391        {
 0392            return;
 393        }
 394
 0395        view.SetIsWeb3(userProfile.hasConnectedWeb3);
 396
 0397        ProcessCatalog(catalog);
 398
 0399        if (avatarIsDirty)
 0400            return;
 401
 0402        EquipBodyShape(bodyShape);
 0403        EquipSkinColor(userProfile.avatar.skinColor);
 0404        EquipHairColor(userProfile.avatar.hairColor);
 0405        EquipEyesColor(userProfile.avatar.eyeColor);
 406
 0407        model.wearables.Clear();
 0408        view.UnselectAllWearables();
 409
 0410        int wearablesCount = userProfile.avatar.wearables.Count;
 411
 0412        if (isPlayerRendererLoaded)
 413        {
 0414            for (var i = 0; i < wearablesCount; i++)
 415            {
 0416                wearablesCatalogService.WearablesCatalog.TryGetValue(userProfile.avatar.wearables[i], out var wearable);
 0417                if (wearable == null)
 418                {
 0419                    Debug.LogError($"Couldn't find wearable with ID {userProfile.avatar.wearables[i]}");
 0420                    continue;
 421                }
 422
 0423                if (wearable.IsEmote())
 0424                    EquipEmote(wearable);
 425                else
 0426                    EquipWearable(wearable);
 427            }
 428        }
 429
 0430        EnsureWearablesCategoriesNotEmpty();
 431
 0432        UpdateAvatarPreview();
 0433        isAvatarPreviewReady = true;
 0434    }
 435
 436    private void EnsureWearablesCategoriesNotEmpty()
 437    {
 0438        var categoriesInUse = model.wearables
 0439            .Where(x => !x.IsEmote())
 0440            .Select(x => x.data.category).ToArray();
 441
 0442        for (var i = 0; i < categoriesThatMustHaveSelection.Length; i++)
 443        {
 0444            var category = categoriesThatMustHaveSelection[i];
 0445            if (category != Categories.BODY_SHAPE && !(categoriesInUse.Contains(category)))
 446            {
 447                WearableItem wearable;
 0448                var defaultItemId = WearableLiterals.DefaultWearables.GetDefaultWearable(model.bodyShape.id, category);
 0449                if (defaultItemId != null)
 450                {
 0451                    wearablesCatalogService.WearablesCatalog.TryGetValue(defaultItemId, out wearable);
 452                }
 453                else
 454                {
 0455                    wearable = wearablesByCategory[category].FirstOrDefault(x => x.SupportsBodyShape(model.bodyShape.id)
 456                }
 457
 0458                if (wearable != null)
 459                {
 0460                    EquipWearable(wearable);
 461                }
 462            }
 463        }
 0464    }
 465
 466    public void WearableClicked(string wearableId)
 467    {
 0468        wearablesCatalogService.WearablesCatalog.TryGetValue(wearableId, out var wearable);
 0469        if (wearable == null) return;
 470
 0471        if (wearable.data.category == Categories.BODY_SHAPE)
 472        {
 0473            if (wearable.id == model.bodyShape.id)
 0474                return;
 0475            EquipBodyShape(wearable);
 476        }
 477        else
 478        {
 0479            if (model.wearables.Contains(wearable))
 480            {
 0481                if (!categoriesThatMustHaveSelection.Contains(wearable.data.category))
 482                {
 0483                    UnequipWearable(wearable);
 484                }
 485                else
 486                {
 0487                    return;
 488                }
 489            }
 490            else
 491            {
 0492                if (IsTryingToReplaceSkin(wearable))
 0493                    UnequipWearable(model.GetWearable(Categories.SKIN));
 494
 0495                var sameCategoryEquipped = model.GetWearable(wearable.data.category);
 0496                if (sameCategoryEquipped != null)
 0497                    UnequipWearable(sameCategoryEquipped);
 498
 0499                EquipWearable(wearable);
 500            }
 501        }
 502
 0503        UpdateAvatarPreview();
 0504        view.AddFeedbackOnAppear();
 0505        avatarIsDirty = true;
 0506    }
 507
 508    public void HairColorClicked(Color color)
 509    {
 0510        EquipHairColor(color);
 0511        view.SelectHairColor(model.hairColor);
 0512        UpdateAvatarPreview();
 0513        view.AddFeedbackOnAppear();
 0514    }
 515
 516    public void SkinColorClicked(Color color)
 517    {
 0518        EquipSkinColor(color);
 0519        view.SelectSkinColor(model.skinColor);
 0520        UpdateAvatarPreview();
 0521    }
 522
 523    public void EyesColorClicked(Color color)
 524    {
 0525        EquipEyesColor(color);
 0526        view.SelectEyeColor(model.eyesColor);
 0527        UpdateAvatarPreview();
 0528    }
 529
 530    protected virtual void UpdateAvatarPreview()
 531    {
 0532        if (bypassUpdateAvatarPreview)
 0533            return;
 534
 0535        AvatarModel modelToUpdate = model.ToAvatarModel();
 536
 537        // We always keep the loaded emotes into the Avatar Preview
 0538        foreach (string emoteId in emotesCustomizationDataStore.currentLoadedEmotes.Get())
 539        {
 0540            modelToUpdate.emotes.Add(new AvatarModel.AvatarEmoteEntry() { urn = emoteId });
 541        }
 542
 0543        view.UpdateAvatarPreview(modelToUpdate);
 0544    }
 545
 546    private void EquipHairColor(Color color)
 547    {
 0548        model.hairColor = color;
 0549        view.SelectHairColor(model.hairColor);
 0550    }
 551
 552    private void EquipEyesColor(Color color)
 553    {
 0554        model.eyesColor = color;
 0555        view.SelectEyeColor(model.eyesColor);
 0556    }
 557
 558    private void EquipSkinColor(Color color)
 559    {
 0560        model.skinColor = color;
 0561        view.SelectSkinColor(model.skinColor);
 0562    }
 563
 564    private void EquipBodyShape(WearableItem bodyShape)
 565    {
 0566        if (bodyShape.data.category != Categories.BODY_SHAPE)
 567        {
 0568            Debug.LogError($"Item ({bodyShape.id} is not a body shape");
 0569            return;
 570        }
 571
 0572        if (model.bodyShape == bodyShape)
 0573            return;
 574
 0575        model.bodyShape = bodyShape;
 0576        emotesCustomizationComponentController.SetEquippedBodyShape(bodyShape.id);
 0577        view.UpdateSelectedBody(bodyShape);
 578
 0579        int wearablesCount = model.wearables.Count;
 0580        for (var i = wearablesCount - 1; i >= 0; i--)
 581        {
 0582            UnequipWearable(model.wearables[i]);
 583        }
 584
 0585        var defaultWearables = WearableLiterals.DefaultWearables.GetDefaultWearables(bodyShape.id);
 0586        for (var i = 0; i < defaultWearables.Length; i++)
 587        {
 0588            if (catalog.TryGetValue(defaultWearables[i], out var wearable))
 0589                EquipWearable(wearable);
 590        }
 0591    }
 592
 593    private void EquipWearable(WearableItem wearable)
 594    {
 0595        if (wearable.IsEmote())
 0596            return;
 597
 0598        if (!wearablesByCategory.ContainsKey(wearable.data.category))
 0599            return;
 600
 0601        if (wearablesByCategory[wearable.data.category].Contains(wearable) && wearable.SupportsBodyShape(model.bodyShape
 602        {
 0603            var toReplace = GetWearablesReplacedBy(wearable);
 0604            toReplace.ForEach(UnequipWearable);
 0605            model.wearables.Add(wearable);
 0606            view.EquipWearable(wearable);
 607        }
 0608    }
 609
 610    private void UnequipWearable(WearableItem wearable)
 611    {
 0612        if (wearable.IsEmote())
 0613            return;
 614
 0615        if (model.wearables.Contains(wearable))
 616        {
 0617            model.wearables.Remove(wearable);
 0618            view.UnequipWearable(wearable);
 0619            avatarIsDirty = true;
 620        }
 0621    }
 622
 623    private void EquipEmote(WearableItem emote)
 624    {
 0625        if (!emote.IsEmote())
 0626            return;
 0627        avatarIsDirty = true;
 0628    }
 629
 630    private void UnequipEmote(WearableItem emote)
 631    {
 0632        if (!emote.IsEmote())
 0633            return;
 634
 0635        avatarIsDirty = true;
 0636    }
 637
 638    public void UnequipAllWearables()
 639    {
 0640        foreach (var wearable in model.wearables)
 641        {
 0642            if (!wearable.IsEmote())
 0643                view.UnequipWearable(wearable);
 644        }
 645
 0646        model.wearables.Clear();
 0647    }
 648
 649    private void ProcessCatalog(BaseDictionary<string, WearableItem> catalog)
 650    {
 0651        wearablesByCategory.Clear();
 0652        view.RemoveAllWearables();
 0653        bool hasSkin = false;
 0654        bool hasCollectible = false;
 0655        using (var iterator = catalog.Get().GetEnumerator())
 656        {
 0657            while (iterator.MoveNext())
 658            {
 0659                if (iterator.Current.Value.IsEmote())
 660                    continue;
 661
 0662                if (iterator.Current.Value.IsFromThirdPartyCollection
 663                    && !thirdPartyCollectionsActive.Contains(iterator.Current.Value.ThirdPartyCollectionId))
 664                    continue;
 665
 0666                AddWearable(iterator.Current.Key, iterator.Current.Value);
 0667                hasSkin = iterator.Current.Value.IsSkin() || hasSkin;
 0668                hasCollectible = iterator.Current.Value.IsCollectible() || hasCollectible;
 669            }
 0670        }
 0671        view.ShowSkinPopulatedList(hasSkin);
 0672        view.ShowCollectiblesPopulatedList(hasCollectible);
 0673        view.RefreshSelectorsSize();
 0674    }
 675
 676    private void AddWearable(string id, WearableItem wearable)
 677    {
 0678        if (!wearable.data.tags.Contains(WearableLiterals.Tags.BASE_WEARABLE) && userProfile.GetItemAmount(id) == 0)
 0679            return;
 680
 0681        if (!wearablesByCategory.ContainsKey(wearable.data.category))
 0682            wearablesByCategory.Add(wearable.data.category, new List<WearableItem>());
 683
 0684        wearablesByCategory[wearable.data.category].Add(wearable);
 0685        view.AddWearable(wearable, userProfile.GetItemAmount(id),
 686            ShouldShowHideOtherWearablesToast,
 687            ShouldShowReplaceOtherWearablesToast,
 688            ShouldShowIncompatibleWearableToast);
 0689    }
 690
 691    private void RemoveWearable(string id, WearableItem wearable)
 692    {
 0693        if (wearablesByCategory.ContainsKey(wearable.data.category))
 694        {
 0695            if (wearablesByCategory[wearable.data.category].Remove(wearable))
 696            {
 0697                if (wearablesByCategory[wearable.data.category].Count == 0)
 698                {
 0699                    wearablesByCategory.Remove(wearable.data.category);
 700                }
 701            }
 702        }
 703
 0704        view.RemoveWearable(wearable);
 0705    }
 706
 707    public void RandomizeWearables()
 708    {
 0709        EquipHairColor(view.GetRandomColor());
 0710        EquipEyesColor(view.GetRandomColor());
 711
 0712        List<WearableItem> wearablesToRemove = model.wearables.Where(x => !x.IsEmote()).ToList();
 0713        foreach (var wearable in wearablesToRemove)
 714        {
 0715            model.wearables.Remove(wearable);
 716        }
 717
 0718        view.UnselectAllWearables();
 0719        using (var iterator = wearablesByCategory.GetEnumerator())
 720        {
 0721            while (iterator.MoveNext())
 722            {
 0723                string category = iterator.Current.Key;
 0724                if (!categoriesToRandomize.Contains(category))
 725                {
 726                    continue;
 727                }
 728
 0729                var supportedWearables = iterator.Current.Value.Where(x => x.SupportsBodyShape(model.bodyShape.id)).ToAr
 0730                if (supportedWearables.Length == 0)
 731                {
 0732                    Debug.LogError($"Couldn't get any wearable for category {category} and bodyshape {model.bodyShape.id
 0733                    continue;
 734                }
 735
 0736                var wearable = supportedWearables[Random.Range(0, supportedWearables.Length - 1)];
 0737                EquipWearable(wearable);
 738            }
 0739        }
 740
 0741        UpdateAvatarPreview();
 0742        view.AddFeedbackOnAppear();
 0743    }
 744
 745    private List<WearableItem> GetWearablesReplacedBy(WearableItem wearableItem)
 746    {
 0747        var wearablesToReplace = new List<WearableItem>();
 0748        var categoriesToReplace = new HashSet<string>(wearableItem.GetReplacesList(model.bodyShape.id) ?? new string[0])
 749
 0750        int wearableCount = model.wearables.Count;
 0751        for (int i = 0; i < wearableCount; i++)
 752        {
 0753            var wearable = model.wearables[i];
 0754            if (wearable == null) continue;
 755
 0756            if (categoriesToReplace.Contains(wearable.data.category))
 757            {
 0758                wearablesToReplace.Add(wearable);
 759            }
 760            else
 761            {
 762                //For retrocompatibility's sake we check current wearables against new one (compatibility matrix is symm
 0763                HashSet<string> replacesList = new HashSet<string>(wearable.GetReplacesList(model.bodyShape.id) ?? new s
 0764                if (replacesList.Contains(wearableItem.data.category))
 765                {
 0766                    wearablesToReplace.Add(wearable);
 767                }
 768            }
 769        }
 770
 0771        return wearablesToReplace;
 772    }
 773
 0774    public void SetVisibility(bool visible) { avatarEditorVisible.Set(visible); }
 775
 0776    private void OnAvatarEditorVisibleChanged(bool current, bool previous) { SetVisibility_Internal(current); }
 777
 778    private void SetVisibility_Internal(bool visible)
 779    {
 0780        bool isSignUpFlow = DataStore.i.common.isSignUpFlow.Get();
 0781        if (!visible && view.isOpen)
 782        {
 0783            view.ResetPreviewEmote();
 784
 0785            if (isSignUpFlow)
 0786                DataStore.i.virtualAudioMixer.sceneSFXVolume.Set(1f);
 787
 0788            Environment.i.messaging.manager.paused = false;
 0789            DataStore.i.skyboxConfig.avatarMatProfile.Set(AvatarMaterialProfile.InWorld);
 0790            if (prevMouseLockState && isSignUpFlow)
 791            {
 0792                Utils.LockCursor();
 793            }
 794
 795            // NOTE(Brian): SSAO doesn't work correctly with the offseted avatar preview if the renderScale != 1.0
 0796            var asset = GraphicsSettings.renderPipelineAsset as UniversalRenderPipelineAsset;
 0797            asset.renderScale = prevRenderScale;
 798
 0799            if (isSignUpFlow)
 0800                CommonScriptableObjects.isFullscreenHUDOpen.Set(false);
 801
 0802            DataStore.i.common.isPlayerRendererLoaded.OnChange -= PlayerRendererLoaded;
 803
 0804            OnClose?.Invoke();
 805        }
 0806        else if (visible && !view.isOpen)
 807        {
 0808            if (isSignUpFlow)
 809            {
 0810                DataStore.i.virtualAudioMixer.sceneSFXVolume.Set(0f);
 0811                view.sectionSelector.Hide(true);
 812            }
 813            else
 814            {
 0815                view.sectionSelector.Show(true);
 816            }
 817
 0818            if (!isSignUpFlow)
 819            {
 0820                LoadOwnedWereables(userProfile);
 0821                LoadOwnedEmotes();
 822            }
 823
 0824            LoadCollections();
 0825            Environment.i.messaging.manager.paused = isSignUpFlow;
 0826            DataStore.i.skyboxConfig.avatarMatProfile.Set(AvatarMaterialProfile.InEditor);
 827
 0828            prevMouseLockState = Utils.IsCursorLocked;
 829
 0830            if (isSignUpFlow || !DataStore.i.exploreV2.isInitialized.Get())
 0831                Utils.UnlockCursor();
 832
 833            // NOTE(Brian): SSAO doesn't work correctly with the offseted avatar preview if the renderScale != 1.0
 0834            var asset = GraphicsSettings.renderPipelineAsset as UniversalRenderPipelineAsset;
 0835            prevRenderScale = asset.renderScale;
 0836            asset.renderScale = 1.0f;
 837
 0838            if (isSignUpFlow)
 0839                CommonScriptableObjects.isFullscreenHUDOpen.Set(true);
 840
 0841            DataStore.i.common.isPlayerRendererLoaded.OnChange += PlayerRendererLoaded;
 842
 0843            OnOpen?.Invoke();
 844        }
 845
 0846        view.SetVisibility(visible);
 0847    }
 848
 849    public void Dispose()
 850    {
 0851        loadEmotesCTS.SafeCancelAndDispose();
 0852        loadEmotesCTS = null;
 853
 0854        loadOwnedWearablesCTS.SafeCancelAndDispose();
 0855        loadOwnedWearablesCTS = null;
 856
 0857        loadThirdPartyWearablesCTS.SafeCancelAndDispose();
 0858        loadThirdPartyWearablesCTS = null;
 859
 0860        avatarEditorVisible.OnChange -= OnAvatarEditorVisibleChanged;
 0861        configureBackpackInFullscreenMenu.OnChange -= ConfigureBackpackInFullscreenMenuChanged;
 0862        DataStore.i.common.isPlayerRendererLoaded.OnChange -= PlayerRendererLoaded;
 0863        exploreV2IsOpen.OnChange -= ExploreV2IsOpenChanged;
 0864        emotesCustomizationDataStore.isEmotesCustomizationSelected.OnChange -= HandleEmotesCostumizationSelection;
 0865        emotesCustomizationDataStore.currentLoadedEmotes.OnAdded -= OnNewEmoteAdded;
 866
 0867        emotesCustomizationComponentController.onEmotePreviewed -= OnPreviewEmote;
 0868        emotesCustomizationComponentController.onEmoteEquipped -= OnEmoteEquipped;
 0869        emotesCustomizationComponentController.onEmoteUnequipped -= OnEmoteUnequipped;
 0870        emotesCustomizationComponentController.onEmoteSell -= OnRedirectToEmoteSelling;
 871
 0872        avatarEditorHUDAnimationController.Dispose();
 0873        Environment.i.serviceLocator.Get<IApplicationFocusService>().OnApplicationFocus -= OnApplicationFocus;
 874
 0875        CleanUp();
 0876    }
 877
 878    public void CleanUp()
 879    {
 0880        UnequipAllWearables();
 881
 0882        if (view != null)
 0883            view.Dispose();
 884
 0885        this.userProfile.OnUpdate -= LoadUserProfile;
 0886        this.catalog.OnAdded -= AddWearable;
 0887        this.catalog.OnRemoved -= RemoveWearable;
 0888        DataStore.i.common.isPlayerRendererLoaded.OnChange -= PlayerRendererLoaded;
 0889    }
 890
 0891    public void SetConfiguration(HUDConfiguration configuration) { SetVisibility(configuration.active); }
 892
 893    public void SaveAvatar(Texture2D face256Snapshot, Texture2D bodySnapshot)
 894    {
 0895        var avatarModel = model.ToAvatarModel();
 896
 897        // Add the equipped emotes to the avatar model
 0898        List<AvatarModel.AvatarEmoteEntry> emoteEntries = new List<AvatarModel.AvatarEmoteEntry>();
 0899        int equippedEmotesCount = emotesCustomizationDataStore.unsavedEquippedEmotes.Count();
 0900        for (int i = 0; i < equippedEmotesCount; i++)
 901        {
 0902            var equippedEmote = emotesCustomizationDataStore.unsavedEquippedEmotes[i];
 0903            if (equippedEmote == null)
 904                continue;
 905
 0906            emoteEntries.Add(new AvatarModel.AvatarEmoteEntry { slot = i, urn = equippedEmote.id });
 907        }
 908
 0909        avatarModel.emotes = emoteEntries;
 910
 0911        SendNewEquippedWearablesAnalytics(userProfile.avatar.wearables, avatarModel.wearables);
 0912        emotesCustomizationDataStore.equippedEmotes.Set(emotesCustomizationDataStore.unsavedEquippedEmotes.Get());
 913
 0914        WebInterface.SendSaveAvatar(avatarModel, face256Snapshot, bodySnapshot, DataStore.i.common.isSignUpFlow.Get());
 0915        userProfile.OverrideAvatar(avatarModel, face256Snapshot);
 916
 0917        if (DataStore.i.common.isSignUpFlow.Get())
 918        {
 0919            DataStore.i.HUDs.signupVisible.Set(true);
 0920            newUserExperienceAnalytics.AvatarEditSuccessNux();
 921        }
 922
 0923        avatarIsDirty = false;
 0924        SetVisibility(false);
 0925    }
 926
 927    public void GoToMarketplaceOrConnectWallet()
 928    {
 0929        if (userProfile.hasConnectedWeb3)
 0930            WebInterface.OpenURL(URL_MARKET_PLACE);
 931        else
 0932            WebInterface.OpenURL(URL_GET_A_WALLET);
 0933    }
 934
 935    public void SellCollectible(string collectibleId)
 936    {
 0937        var ownedCollectible = ownedNftCollectionsL1.FirstOrDefault(nft => nft.urn == collectibleId);
 0938        if (ownedCollectible == null)
 0939            ownedCollectible = ownedNftCollectionsL2.FirstOrDefault(nft => nft.urn == collectibleId);
 940
 0941        if (ownedCollectible != null)
 0942            WebInterface.OpenURL(URL_SELL_SPECIFIC_COLLECTIBLE.Replace("{collectionId}", ownedCollectible.collectionId).
 943        else
 0944            WebInterface.OpenURL(URL_SELL_COLLECTIBLE_GENERIC);
 0945    }
 946
 0947    public void ToggleVisibility() { SetVisibility(!view.isOpen); }
 948
 0949    private void ConfigureBackpackInFullscreenMenuChanged(Transform currentParentTransform, Transform previousParentTran
 950
 951    private void ExploreV2IsOpenChanged(bool current, bool previous)
 952    {
 0953        if (!current && avatarIsDirty)
 954        {
 0955            avatarIsDirty = false;
 956
 0957            LoadUserProfile(userProfile, true);
 958
 0959            emotesCustomizationComponentController.RestoreEmoteSlots();
 960        }
 0961    }
 962
 963    private void LoadCollections()
 964    {
 0965        if (!isThirdPartyCollectionsEnabled || collectionsAlreadyLoaded)
 0966            return;
 967
 0968        WearablesFetchingHelper.GetThirdPartyCollections()
 969            .Then((collections) =>
 970            {
 0971                view.LoadCollectionsDropdown(collections);
 0972                collectionsAlreadyLoaded = true;
 0973                LoadUserThirdPartyWearables();
 0974            })
 0975            .Catch((error) => Debug.LogError(error));
 0976    }
 977
 978    private void LoadUserThirdPartyWearables()
 979    {
 0980        List<string> collectionIdsToLoad = new List<string>();
 0981        foreach (string wearableId in userProfile.avatar.wearables)
 982        {
 0983            wearablesCatalogService.WearablesCatalog.TryGetValue(wearableId, out var wearable);
 984
 0985            if (wearable != null && wearable.IsFromThirdPartyCollection)
 986            {
 0987                if (!collectionIdsToLoad.Contains(wearable.ThirdPartyCollectionId))
 0988                    collectionIdsToLoad.Add(wearable.ThirdPartyCollectionId);
 989            }
 990        }
 991
 0992        foreach (string collectionId in collectionIdsToLoad)
 993        {
 0994            view.ToggleThirdPartyCollection(collectionId, true);
 995        }
 0996    }
 997
 998    public void ToggleThirdPartyCollection(bool isOn, string collectionId, string _)
 999    {
 01000        if (isOn)
 01001            FetchAndShowThirdPartyCollection(collectionId);
 1002        else
 01003            RemoveThirdPartyCollection(collectionId);
 01004    }
 1005
 1006    private void FetchAndShowThirdPartyCollection(string collectionId)
 1007    {
 1008        async UniTaskVoid RequestThirdPartyWearablesAsync(CancellationToken ct)
 1009        {
 1010            try
 1011            {
 01012                var wearables = await wearablesCatalogService.RequestThirdPartyWearablesByCollectionAsync(
 1013                    userProfile.userId,
 1014                    collectionId,
 1015                    1,
 1016                    int.MaxValue,
 1017                    true,
 1018                    ct);
 1019
 01020                if (wearables.wearables.Count.Equals(0)) view.ShowNoItemOfWearableCollectionWarning();
 01021                thirdPartyCollectionsActive.Add(collectionId);
 01022                foreach (var wearable in wearables.wearables)
 1023                {
 01024                    if (!userProfile.ContainsInInventory(wearable.id))
 1025                    {
 01026                        userProfile.AddToInventory(wearable.id);
 1027
 01028                        if (!thirdPartyWearablesLoaded.Contains(wearable.id))
 01029                            thirdPartyWearablesLoaded.Add(wearable.id);
 1030                    }
 1031                }
 1032
 01033                view.BlockCollectionsDropdown(false);
 01034                LoadUserProfile(userProfile, true);
 01035                view.RefreshSelectorsSize();
 01036            }
 1037            catch (Exception e)
 1038            {
 01039                view.BlockCollectionsDropdown(false);
 01040                Debug.LogError(e.Message);
 01041            }
 01042        }
 1043
 01044        view.BlockCollectionsDropdown(true);
 01045        RequestThirdPartyWearablesAsync(loadThirdPartyWearablesCTS.Token).Forget();
 01046    }
 1047
 1048    private void RemoveThirdPartyCollection(string collectionId)
 1049    {
 01050        var wearablesToRemove = wearablesCatalogService.WearablesCatalog
 01051                                                       .Where(wearable => !userProfile.HasEquipped(wearable.Key)
 1052                                                                          && wearable.Value.ThirdPartyCollectionId == co
 01053                                                       .Select(item => item.Key)
 1054                                                       .ToList();
 1055
 01056        thirdPartyCollectionsActive.Remove(collectionId);
 1057
 01058        foreach (string wearableId in wearablesToRemove)
 1059        {
 01060            wearablesCatalogService.RemoveWearableFromCatalog(wearableId);
 01061            userProfile.RemoveFromInventory(wearableId);
 01062            thirdPartyWearablesLoaded.Remove(wearableId);
 1063        }
 1064
 01065        LoadUserProfile(userProfile, true);
 01066    }
 1067
 1068    private bool ShouldShowHideOtherWearablesToast(WearableItem wearable)
 1069    {
 01070        var isWearingSkinAlready = model.wearables.Any(item => item.IsSkin());
 01071        return wearable.IsSkin() && !isWearingSkinAlready;
 1072    }
 1073
 1074    private bool ShouldShowIncompatibleWearableToast(WearableItem wearable)
 1075    {
 01076        if(wearable.data.category == Categories.BODY_SHAPE || wearable.data.category == Categories.SKIN)
 01077            return false;
 1078        else
 01079            return !wearable.SupportsBodyShape(model.bodyShape.id);
 1080    }
 1081
 1082    private bool IsTryingToReplaceSkin(WearableItem wearable)
 1083    {
 01084        return model.wearables.Any(skin =>
 1085        {
 01086            return skin.IsSkin()
 1087                   && skin.DoesHide(wearable.data.category, model.bodyShape.id);
 1088        });
 1089    }
 1090
 1091    private bool ShouldShowReplaceOtherWearablesToast(WearableItem wearable)
 1092    {
 01093        if (IsTryingToReplaceSkin(wearable)) return true;
 01094        var toReplace = GetWearablesReplacedBy(wearable);
 01095        if (wearable == null || toReplace.Count == 0) return false;
 01096        if (model.wearables.Contains(wearable)) return false;
 1097
 1098        // NOTE: why just 1?
 01099        if (toReplace.Count == 1)
 1100        {
 01101            var w = toReplace[0];
 01102            if (w.data.category == wearable.data.category)
 01103                return false;
 1104        }
 01105        return true;
 1106    }
 1107
 1108
 1109    private void HandleEmotesCostumizationSelection(bool current, bool previous)
 1110    {
 01111        if (!current)
 01112            return;
 1113
 01114        view.sectionSelector.GetSection(AvatarEditorHUDView.EMOTES_SECTION_INDEX).SelectToggle();
 01115    }
 1116
 1117    private void OnNewEmoteAdded(string emoteId)
 1118    {
 01119        if (!isAvatarPreviewReady)
 01120            return;
 1121
 01122        UpdateAvatarPreview();
 01123    }
 1124
 01125    private void OnPreviewEmote(string emoteId) { view.PlayPreviewEmote(emoteId); }
 1126
 1127    private void OnEmoteEquipped(string emoteId)
 1128    {
 01129        catalog.TryGetValue(emoteId, out WearableItem equippedEmote);
 1130
 01131        if (equippedEmote != null)
 01132            EquipEmote(equippedEmote);
 01133    }
 1134
 1135    private void OnEmoteUnequipped(string emoteId)
 1136    {
 01137        catalog.TryGetValue(emoteId, out WearableItem unequippedEmote);
 1138
 01139        if (unequippedEmote != null)
 01140            UnequipEmote(unequippedEmote);
 01141    }
 1142
 01143    private void OnRedirectToEmoteSelling(string emoteId) { SellCollectible(emoteId); }
 1144
 1145    internal void SendNewEquippedWearablesAnalytics(List<string> oldWearables, List<string> newWearables)
 1146    {
 01147        for (int i = 0; i < newWearables.Count; i++)
 1148        {
 01149            if (oldWearables.Contains(newWearables[i]))
 1150                continue;
 1151
 01152            catalog.TryGetValue(newWearables[i], out WearableItem newEquippedEmote);
 01153            if (newEquippedEmote != null && !newEquippedEmote.IsEmote())
 01154                SendEquipWearableAnalytic(newEquippedEmote);
 1155        }
 01156    }
 1157
 1158    private void SendEquipWearableAnalytic(WearableItem equippedWearable)
 1159    {
 01160        Dictionary<string, string> data = new Dictionary<string, string>();
 01161        data.Add("name", equippedWearable.GetName());
 01162        data.Add("rarity", equippedWearable.rarity);
 01163        data.Add("category", equippedWearable.data.category);
 01164        data.Add("linked_wearable", equippedWearable.IsFromThirdPartyCollection.ToString());
 01165        data.Add("third_party_collection_id", equippedWearable.ThirdPartyCollectionId);
 01166        data.Add("is_in_l2", equippedWearable.IsInL2().ToString());
 01167        data.Add("smart_item", equippedWearable.IsSmart().ToString());
 1168
 01169        analytics.SendAnalytic(EQUIP_WEARABLE_METRIC, data);
 01170    }
 1171
 1172    private bool IsWearableUpdateInCooldown()
 1173    {
 01174        return Time.realtimeSinceStartup < lastTimeOwnedWearablesChecked + REQUESTS_COOLDOWN_TIME;
 1175    }
 1176
 1177    private bool IsEmotesUpdateInCooldown()
 1178    {
 01179        return Time.realtimeSinceStartup < lastTimeOwnedEmotesChecked + REQUESTS_COOLDOWN_TIME;
 1180    }
 1181
 1182    private void OnApplicationFocus()
 1183    {
 01184        lastTimeOwnedWearablesChecked = -REQUESTS_COOLDOWN_TIME;
 01185        lastTimeOwnedEmotesChecked = -REQUESTS_COOLDOWN_TIME;
 01186    }
 1187}

Methods/Properties

AvatarEditorHUDController()
userProfile()
renderingEnabled()
isPlayerRendererLoaded()
avatarEditorVisible()
configureBackpackInFullscreenMenu()
exploreV2IsOpen()
emotesCustomizationDataStore()
AvatarEditorHUDController(DCL.DataStore_FeatureFlag, IAnalytics, DCLServices.WearablesCatalogService.IWearablesCatalogService, IUserProfileBridge)
isThirdPartyCollectionsEnabled()
Initialize(System.Boolean, MainScripts.DCL.Controllers.HUD.CharacterPreview.IPreviewCameraRotationController)
InitializeAsyncEmoteController()
SetCatalog(.BaseDictionary[String,WearableItem])
OnAdditionalWearableRemoved(System.String, WearableItem)
OnAdditionalWearableAdded(System.String, WearableItem)
LoadUserProfile(UserProfile)
>c__DisplayClass72_0/<<LoadOwnedWereables()
LoadOwnedWereables(UserProfile)
LoadOwnedEmotes()
LoadOwnedEmotesTask()
QueryNftCollections(System.String)
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)
>c__DisplayClass113_0/<<FetchAndShowThirdPartyCollection()
FetchAndShowThirdPartyCollection(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)
IsWearableUpdateInCooldown()
IsEmotesUpdateInCooldown()
OnApplicationFocus()