< 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:326
Uncovered lines:165
Coverable lines:491
Total lines:980
Line coverage:66.3% (326 of 491)
Covered branches:0
Total branches:0

Metrics

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

File(s)

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

#LineLine coverage
 1using DCL;
 2using DCL.EmotesCustomization;
 3using DCL.Helpers;
 4using DCL.Interface;
 5using DCL.NotificationModel;
 6using System;
 7using System.Collections.Generic;
 8using System.Linq;
 9using UnityEngine;
 10using UnityEngine.Rendering;
 11using UnityEngine.Rendering.Universal;
 12using Categories = WearableLiterals.Categories;
 13using Environment = DCL.Environment;
 14using Random = UnityEngine.Random;
 15using Type = DCL.NotificationModel.Type;
 16
 17public class AvatarEditorHUDController : IHUD
 18{
 19    private const int LOADING_OWNED_WEARABLES_RETRIES = 3;
 20    private const string LOADING_OWNED_WEARABLES_ERROR_MESSAGE = "There was a problem loading your wearables";
 21    private const string URL_MARKET_PLACE = "https://market.decentraland.org/browse?section=wearables";
 22    private const string URL_GET_A_WALLET = "https://docs.decentraland.org/get-a-wallet";
 23    private const string URL_SELL_COLLECTIBLE_GENERIC = "https://market.decentraland.org/account";
 24    private const string URL_SELL_SPECIFIC_COLLECTIBLE = "https://market.decentraland.org/contracts/{collectionId}/token
 25    private const string THIRD_PARTY_COLLECTIONS_FEATURE_FLAG = "third_party_collections";
 26    internal const string EQUIP_WEARABLE_METRIC = "equip_wearable";
 127    protected static readonly string[] categoriesThatMustHaveSelection = { Categories.BODY_SHAPE, Categories.UPPER_BODY,
 128    protected static readonly string[] categoriesToRandomize = { Categories.HAIR, Categories.EYES, Categories.EYEBROWS, 
 29
 30    [NonSerialized]
 31    public bool bypassUpdateAvatarPreview = false;
 32
 33    internal UserProfile userProfile;
 34    internal IAnalytics analytics;
 35    private BaseDictionary<string, WearableItem> catalog;
 10636    bool renderingEnabled => CommonScriptableObjects.rendererState.Get();
 10337    bool isPlayerRendererLoaded => DataStore.i.common.isPlayerRendererLoaded.Get();
 18338    BaseVariable<bool> avatarEditorVisible => DataStore.i.HUDs.avatarEditorVisible;
 13939    BaseVariable<Transform> configureBackpackInFullscreenMenu => DataStore.i.exploreV2.configureBackpackInFullscreenMenu
 9140    BaseVariable<bool> exploreV2IsOpen => DataStore.i.exploreV2.isOpen;
 30941    DataStore_EmotesCustomization emotesCustomizationDataStore => DataStore.i.emotesCustomization;
 042    DataStore_FeatureFlag featureFlagsDataStore => DataStore.i.featureFlags;
 43
 44    private readonly DataStore_FeatureFlag featureFlags;
 45
 4846    private readonly Dictionary<string, List<WearableItem>> wearablesByCategory = new Dictionary<string, List<WearableIt
 4847    protected readonly AvatarEditorHUDModel model = new AvatarEditorHUDModel();
 48
 49    private ColorList skinColorList;
 50    private ColorList eyeColorList;
 51    private ColorList hairColorList;
 52    private bool prevMouseLockState = false;
 4853    private int ownedWearablesRemainingRequests = LOADING_OWNED_WEARABLES_RETRIES;
 54    private bool ownedWearablesAlreadyLoaded = false;
 4855    private List<Nft> ownedNftCollectionsL1 = new List<Nft>();
 4856    private List<Nft> ownedNftCollectionsL2 = new List<Nft>();
 57    private bool avatarIsDirty = false;
 58    private float lastTimeOwnedWearablesChecked = 0;
 59    internal bool collectionsAlreadyLoaded = false;
 4860    private float prevRenderScale = 1.0f;
 61    private bool isAvatarPreviewReady;
 4862    private List<string> thirdPartyWearablesLoaded = new List<string>();
 63
 64    internal IEmotesCustomizationComponentController emotesCustomizationComponentController;
 65
 9166    private bool isThirdPartyCollectionsEnabled => featureFlags.flags.Get().IsFeatureEnabled(THIRD_PARTY_COLLECTIONS_FEA
 67
 68    public AvatarEditorHUDView view;
 69
 70    public event Action OnOpen;
 71    public event Action OnClose;
 72
 4873    public AvatarEditorHUDController(DataStore_FeatureFlag featureFlags, IAnalytics analytics)
 74    {
 4875        this.featureFlags = featureFlags;
 4876        this.analytics = analytics;
 4877    }
 78
 79    public void Initialize(UserProfile userProfile, BaseDictionary<string, WearableItem> catalog, bool bypassUpdateAvata
 80    {
 4881        this.userProfile = userProfile;
 4882        this.bypassUpdateAvatarPreview = bypassUpdateAvatarPreview;
 83
 4884        view = AvatarEditorHUDView.Create(this);
 85
 4886        view.skinsFeatureContainer.SetActive(true);
 4887        avatarEditorVisible.OnChange += OnAvatarEditorVisibleChanged;
 4888        OnAvatarEditorVisibleChanged(avatarEditorVisible.Get(), false);
 89
 4890        configureBackpackInFullscreenMenu.OnChange += ConfigureBackpackInFullscreenMenuChanged;
 4891        ConfigureBackpackInFullscreenMenuChanged(configureBackpackInFullscreenMenu.Get(), null);
 92
 4893        exploreV2IsOpen.OnChange += ExploreV2IsOpenChanged;
 94
 4895        skinColorList = Resources.Load<ColorList>("SkinTone");
 4896        hairColorList = Resources.Load<ColorList>("HairColor");
 4897        eyeColorList = Resources.Load<ColorList>("EyeColor");
 4898        view.SetColors(skinColorList.colors, hairColorList.colors, eyeColorList.colors);
 99
 48100        SetCatalog(catalog);
 101
 48102        LoadUserProfile(userProfile, true);
 48103        this.userProfile.OnUpdate += LoadUserProfile;
 104
 48105        view.SetSectionActive(AvatarEditorHUDView.EMOTES_SECTION_INDEX, false);
 106
 48107        emotesCustomizationComponentController = CreateEmotesController();
 48108        IEmotesCustomizationComponentView emotesSectionView = emotesCustomizationComponentController.Initialize(
 109            DataStore.i.emotesCustomization,
 110            DataStore.i.emotes,
 111            DataStore.i.exploreV2,
 112            DataStore.i.HUDs,
 113            userProfile,
 114            catalog);
 48115        emotesSectionView.viewTransform.SetParent(view.emotesSection.transform, false);
 48116        view.SetSectionActive(AvatarEditorHUDView.EMOTES_SECTION_INDEX, true);
 117
 48118        emotesCustomizationDataStore.isEmotesCustomizationSelected.OnChange += HandleEmotesCostumizationSelection;
 48119        emotesCustomizationDataStore.currentLoadedEmotes.OnAdded += OnNewEmoteAdded;
 120
 48121        emotesCustomizationComponentController.onEmotePreviewed += OnPreviewEmote;
 48122        emotesCustomizationComponentController.onEmoteEquipped += OnEmoteEquipped;
 48123        emotesCustomizationComponentController.onEmoteUnequipped += OnEmoteUnequipped;
 48124        emotesCustomizationComponentController.onEmoteSell += OnRedirectToEmoteSelling;
 125
 48126        DataStore.i.HUDs.isAvatarEditorInitialized.Set(true);
 127
 48128        view.SetThirdPartyCollectionsVisibility(isThirdPartyCollectionsEnabled);
 48129    }
 130
 131    public void SetCatalog(BaseDictionary<string, WearableItem> catalog)
 132    {
 48133        if (this.catalog != null)
 134        {
 0135            this.catalog.OnAdded -= AddWearable;
 0136            this.catalog.OnRemoved -= RemoveWearable;
 137        }
 138
 48139        this.catalog = catalog;
 140
 48141        ProcessCatalog(this.catalog);
 48142        this.catalog.OnAdded += AddWearable;
 48143        this.catalog.OnRemoved += RemoveWearable;
 48144    }
 145
 146    private void LoadUserProfile(UserProfile userProfile)
 147    {
 43148        LoadUserProfile(userProfile, false);
 43149        QueryNftCollections(userProfile.userId);
 43150    }
 151
 152    private void LoadOwnedWereables(UserProfile userProfile)
 153    {
 154        // If there is more than 1 minute that we have checked the owned wearables, we try it again
 155        // This is done in order to retrieved the wearables after you has claimed them
 43156        if ((Time.realtimeSinceStartup < lastTimeOwnedWearablesChecked + 60 &&
 157             (ownedWearablesAlreadyLoaded ||
 158              ownedWearablesRemainingRequests <= 0)) ||
 159            string.IsNullOrEmpty(userProfile.userId))
 42160            return;
 161
 1162        view.ShowCollectiblesLoadingSpinner(true);
 1163        view.ShowCollectiblesLoadingRetry(false);
 1164        lastTimeOwnedWearablesChecked = Time.realtimeSinceStartup;
 165
 1166        CatalogController.RequestOwnedWearables(userProfile.userId)
 167                         .Then((ownedWearables) =>
 168                         {
 0169                             ownedWearablesAlreadyLoaded = true;
 0170                             this.userProfile.SetInventory(ownedWearables.Select(x => x.id).Concat(thirdPartyWearablesLo
 0171                             LoadUserProfile(userProfile, true);
 0172                             view.ShowCollectiblesLoadingSpinner(false);
 0173                             view.ShowSkinPopulatedList(ownedWearables.Any(item => item.IsSkin()));
 0174                         })
 175                         .Catch((error) =>
 176                         {
 0177                             ownedWearablesRemainingRequests--;
 0178                             if (ownedWearablesRemainingRequests > 0)
 179                             {
 0180                                 Debug.LogWarning("Retrying owned wereables loading...");
 0181                                 LoadOwnedWereables(userProfile);
 0182                             }
 183                             else
 184                             {
 0185                                 NotificationsController.i.ShowNotification(new Model
 186                                 {
 187                                     message = LOADING_OWNED_WEARABLES_ERROR_MESSAGE,
 188                                     type = Type.GENERIC,
 189                                     timer = 10f,
 190                                     destroyOnFinish = true
 191                                 });
 192
 0193                                 view.ShowCollectiblesLoadingSpinner(false);
 0194                                 view.ShowCollectiblesLoadingRetry(true);
 0195                                 Debug.LogError(error);
 196                             }
 0197                         });
 1198    }
 199
 200    private void QueryNftCollections(string userId)
 201    {
 43202        if (string.IsNullOrEmpty(userId))
 43203            return;
 204
 0205        Environment.i.platform.serviceProviders.theGraph.QueryNftCollections(userProfile.userId, NftCollectionsLayer.ETH
 0206           .Then((nfts) => ownedNftCollectionsL1 = nfts)
 0207           .Catch((error) => Debug.LogError(error));
 208
 0209        Environment.i.platform.serviceProviders.theGraph.QueryNftCollections(userProfile.userId, NftCollectionsLayer.MAT
 0210           .Then((nfts) => ownedNftCollectionsL2 = nfts)
 0211           .Catch((error) => Debug.LogError(error));
 0212    }
 213
 214    public void RetryLoadOwnedWearables()
 215    {
 0216        ownedWearablesRemainingRequests = LOADING_OWNED_WEARABLES_RETRIES;
 0217        LoadOwnedWereables(userProfile);
 0218    }
 219
 220    private void PlayerRendererLoaded(bool current, bool previous)
 221    {
 15222        if (!current)
 0223            return;
 224
 15225        if (!ownedWearablesAlreadyLoaded)
 226        {
 15227            List<string> equippedOwnedWearables = new List<string>();
 78228            for (int i = 0; i < userProfile.avatar.wearables.Count; i++)
 229            {
 24230                if (catalog.TryGetValue(userProfile.avatar.wearables[i], out WearableItem wearable) &&
 231                    !wearable.data.tags.Contains(WearableLiterals.Tags.BASE_WEARABLE))
 232                {
 0233                    equippedOwnedWearables.Add(userProfile.avatar.wearables[i]);
 234                }
 235            }
 236
 15237            userProfile.SetInventory(equippedOwnedWearables.ToArray());
 238        }
 239
 15240        LoadUserProfile(userProfile, true);
 15241        DataStore.i.common.isPlayerRendererLoaded.OnChange -= PlayerRendererLoaded;
 15242    }
 243
 244    public void LoadUserProfile(UserProfile userProfile, bool forceLoading)
 245    {
 106246        bool avatarEditorNotVisible = renderingEnabled && !view.isOpen;
 106247        bool isPlaying = !Application.isBatchMode;
 248
 106249        if (!forceLoading)
 250        {
 43251            if (isPlaying && avatarEditorNotVisible)
 0252                return;
 253        }
 254
 106255        if (userProfile == null)
 0256            return;
 257
 106258        if (userProfile.avatar == null || string.IsNullOrEmpty(userProfile.avatar.bodyShape))
 3259            return;
 260
 103261        view.InitializeNavigationEvents(!userProfile.hasConnectedWeb3);
 262
 103263        CatalogController.wearableCatalog.TryGetValue(userProfile.avatar.bodyShape, out var bodyShape);
 264
 103265        if (bodyShape == null)
 266        {
 0267            return;
 268        }
 269
 103270        view.SetIsWeb3(userProfile.hasConnectedWeb3);
 271
 103272        ProcessCatalog(this.catalog);
 103273        EquipBodyShape(bodyShape);
 103274        EquipSkinColor(userProfile.avatar.skinColor);
 103275        EquipHairColor(userProfile.avatar.hairColor);
 103276        EquipEyesColor(userProfile.avatar.eyeColor);
 277
 103278        model.wearables.Clear();
 103279        view.UnselectAllWearables();
 280
 103281        int wearablesCount = userProfile.avatar.wearables.Count;
 282
 103283        if (isPlayerRendererLoaded)
 284        {
 180285            for (var i = 0; i < wearablesCount; i++)
 286            {
 55287                CatalogController.wearableCatalog.TryGetValue(userProfile.avatar.wearables[i], out var wearable);
 55288                if (wearable == null)
 289                {
 0290                    Debug.LogError($"Couldn't find wearable with ID {userProfile.avatar.wearables[i]}");
 0291                    continue;
 292                }
 293
 55294                if (wearable.IsEmote())
 0295                    EquipEmote(wearable);
 296                else
 55297                    EquipWearable(wearable);
 298            }
 299        }
 300
 103301        EnsureWearablesCategoriesNotEmpty();
 302
 103303        UpdateAvatarPreview(true);
 103304        isAvatarPreviewReady = true;
 103305    }
 306
 307    private void EnsureWearablesCategoriesNotEmpty()
 308    {
 103309        var categoriesInUse = model.wearables
 55310            .Where(x => !x.IsEmote())
 55311            .Select(x => x.data.category).ToArray();
 312
 1648313        for (var i = 0; i < categoriesThatMustHaveSelection.Length; i++)
 314        {
 721315            var category = categoriesThatMustHaveSelection[i];
 721316            if (category != Categories.BODY_SHAPE && !(categoriesInUse.Contains(category)))
 317            {
 318                WearableItem wearable;
 576319                var defaultItemId = WearableLiterals.DefaultWearables.GetDefaultWearable(model.bodyShape.id, category);
 576320                if (defaultItemId != null)
 321                {
 576322                    CatalogController.wearableCatalog.TryGetValue(defaultItemId, out wearable);
 576323                }
 324                else
 325                {
 0326                    wearable = wearablesByCategory[category].FirstOrDefault(x => x.SupportsBodyShape(model.bodyShape.id)
 327                }
 328
 576329                if (wearable != null)
 330                {
 576331                    EquipWearable(wearable);
 332                }
 333            }
 334        }
 103335    }
 336
 337    public void WearableClicked(string wearableId)
 338    {
 20339        CatalogController.wearableCatalog.TryGetValue(wearableId, out var wearable);
 23340        if (wearable == null) return;
 341
 17342        if (wearable.data.category == Categories.BODY_SHAPE)
 343        {
 3344            if (wearable.id == model.bodyShape.id)
 0345                return;
 3346            EquipBodyShape(wearable);
 3347        }
 348        else
 349        {
 14350            if (model.wearables.Contains(wearable))
 351            {
 2352                if (!categoriesThatMustHaveSelection.Contains(wearable.data.category))
 353                {
 0354                    UnequipWearable(wearable);
 0355                }
 356                else
 357                {
 2358                    return;
 359                }
 360            }
 361            else
 362            {
 12363                if (IsTryingToReplaceSkin(wearable))
 0364                    UnequipWearable(model.GetWearable(Categories.SKIN));
 365
 12366                var sameCategoryEquipped = model.GetWearable(wearable.data.category);
 12367                if (sameCategoryEquipped != null)
 2368                    UnequipWearable(sameCategoryEquipped);
 369
 12370                EquipWearable(wearable);
 371            }
 372        }
 373
 15374        UpdateAvatarPreview(false);
 15375    }
 376
 377    public void HairColorClicked(Color color)
 378    {
 2379        EquipHairColor(color);
 2380        view.SelectHairColor(model.hairColor);
 2381        UpdateAvatarPreview(true);
 2382    }
 383
 384    public void SkinColorClicked(Color color)
 385    {
 2386        EquipSkinColor(color);
 2387        view.SelectSkinColor(model.skinColor);
 2388        UpdateAvatarPreview(true);
 2389    }
 390
 391    public void EyesColorClicked(Color color)
 392    {
 2393        EquipEyesColor(color);
 2394        view.SelectEyeColor(model.eyesColor);
 2395        UpdateAvatarPreview(true);
 2396    }
 397
 398    protected virtual void UpdateAvatarPreview(bool skipAudio)
 399    {
 125400        if (bypassUpdateAvatarPreview)
 0401            return;
 402
 125403        AvatarModel modelToUpdate = model.ToAvatarModel();
 404
 405        // We always keep the loaded emotes into the Avatar Preview
 250406        foreach (string emoteId in emotesCustomizationDataStore.currentLoadedEmotes.Get())
 407        {
 0408            if (!modelToUpdate.wearables.Contains(emoteId))
 0409                modelToUpdate.wearables.Add(emoteId);
 410        }
 411
 125412        view.UpdateAvatarPreview(modelToUpdate, skipAudio);
 125413    }
 414
 415    private void EquipHairColor(Color color)
 416    {
 106417        model.hairColor = color;
 106418        view.SelectHairColor(model.hairColor);
 106419    }
 420
 421    private void EquipEyesColor(Color color)
 422    {
 106423        model.eyesColor = color;
 106424        view.SelectEyeColor(model.eyesColor);
 106425    }
 426
 427    private void EquipSkinColor(Color color)
 428    {
 105429        model.skinColor = color;
 105430        view.SelectSkinColor(model.skinColor);
 105431    }
 432
 433    private void EquipBodyShape(WearableItem bodyShape)
 434    {
 106435        if (bodyShape.data.category != Categories.BODY_SHAPE)
 436        {
 0437            Debug.LogError($"Item ({bodyShape.id} is not a body shape");
 0438            return;
 439        }
 440
 106441        if (model.bodyShape == bodyShape)
 53442            return;
 443
 53444        model.bodyShape = bodyShape;
 53445        view.UpdateSelectedBody(bodyShape);
 446
 53447        int wearablesCount = model.wearables.Count;
 164448        for (var i = wearablesCount - 1; i >= 0; i--)
 449        {
 29450            UnequipWearable(model.wearables[i]);
 451        }
 452
 53453        var defaultWearables = WearableLiterals.DefaultWearables.GetDefaultWearables(bodyShape.id);
 860454        for (var i = 0; i < defaultWearables.Length; i++)
 455        {
 377456            if (catalog.TryGetValue(defaultWearables[i], out var wearable))
 377457                EquipWearable(wearable);
 458        }
 53459    }
 460
 461    private void EquipWearable(WearableItem wearable)
 462    {
 1027463        if (wearable.IsEmote())
 0464            return;
 465
 1027466        if (!wearablesByCategory.ContainsKey(wearable.data.category))
 0467            return;
 468
 1027469        if (wearablesByCategory[wearable.data.category].Contains(wearable) && wearable.SupportsBodyShape(model.bodyShape
 470        {
 1027471            var toReplace = GetWearablesReplacedBy(wearable);
 1027472            toReplace.ForEach(UnequipWearable);
 1027473            model.wearables.Add(wearable);
 1027474            view.EquipWearable(wearable);
 1027475            avatarIsDirty = true;
 476        }
 1027477    }
 478
 479    private void UnequipWearable(WearableItem wearable)
 480    {
 33481        if (wearable.IsEmote())
 0482            return;
 483
 33484        if (model.wearables.Contains(wearable))
 485        {
 33486            model.wearables.Remove(wearable);
 33487            view.UnequipWearable(wearable);
 33488            avatarIsDirty = true;
 489        }
 33490    }
 491
 492    private void EquipEmote(WearableItem emote)
 493    {
 0494        if (!emote.IsEmote())
 0495            return;
 496
 0497        if (!model.wearables.Contains(emote))
 498        {
 0499            model.wearables.Add(emote);
 0500            avatarIsDirty = true;
 501        }
 0502    }
 503
 504    private void UnequipEmote(WearableItem emote)
 505    {
 0506        if (!emote.IsEmote())
 0507            return;
 508
 0509        if (model.wearables.Contains(emote))
 510        {
 0511            model.wearables.Remove(emote);
 0512            avatarIsDirty = true;
 513        }
 0514    }
 515
 516    public void UnequipAllWearables()
 517    {
 1012518        foreach (var wearable in model.wearables)
 519        {
 431520            if (!wearable.IsEmote())
 431521                view.UnequipWearable(wearable);
 522        }
 523
 75524        model.wearables.Clear();
 75525    }
 526
 527    private void ProcessCatalog(BaseDictionary<string, WearableItem> catalog)
 528    {
 151529        wearablesByCategory.Clear();
 151530        view.RemoveAllWearables();
 151531        using (var iterator = catalog.Get().GetEnumerator())
 532        {
 5551533            while (iterator.MoveNext())
 534            {
 5400535                if (iterator.Current.Value.IsEmote())
 536                    continue;
 537
 5400538                AddWearable(iterator.Current.Key, iterator.Current.Value);
 539            }
 151540        }
 541
 151542        view.RefreshSelectorsSize();
 151543    }
 544
 545    private void AddWearable(string id, WearableItem wearable)
 546    {
 5411547        if (!wearable.data.tags.Contains(WearableLiterals.Tags.BASE_WEARABLE) && userProfile.GetItemAmount(id) == 0)
 1190548            return;
 549
 4221550        if (!wearablesByCategory.ContainsKey(wearable.data.category))
 1650551            wearablesByCategory.Add(wearable.data.category, new List<WearableItem>());
 552
 4221553        wearablesByCategory[wearable.data.category].Add(wearable);
 4221554        view.AddWearable(wearable, userProfile.GetItemAmount(id),
 555            ShouldShowHideOtherWearablesToast,
 556            ShouldShowReplaceOtherWearablesToast);
 4221557    }
 558
 559    private void RemoveWearable(string id, WearableItem wearable)
 560    {
 0561        if (wearablesByCategory.ContainsKey(wearable.data.category))
 562        {
 0563            if (wearablesByCategory[wearable.data.category].Remove(wearable))
 564            {
 0565                if (wearablesByCategory[wearable.data.category].Count == 0)
 566                {
 0567                    wearablesByCategory.Remove(wearable.data.category);
 568                }
 569            }
 570        }
 571
 0572        view.RemoveWearable(wearable);
 0573    }
 574
 575    public void RandomizeWearables()
 576    {
 1577        EquipHairColor(view.GetRandomColor());
 1578        EquipEyesColor(view.GetRandomColor());
 579
 7580        List<WearableItem> wearablesToRemove = model.wearables.Where(x => !x.IsEmote()).ToList();
 14581        foreach (var wearable in wearablesToRemove)
 582        {
 6583            model.wearables.Remove(wearable);
 584        }
 585
 1586        view.UnselectAllWearables();
 1587        using (var iterator = wearablesByCategory.GetEnumerator())
 588        {
 12589            while (iterator.MoveNext())
 590            {
 11591                string category = iterator.Current.Key;
 11592                if (!categoriesToRandomize.Contains(category))
 593                {
 594                    continue;
 595                }
 596
 29597                var supportedWearables = iterator.Current.Value.Where(x => x.SupportsBodyShape(model.bodyShape.id)).ToAr
 7598                if (supportedWearables.Length == 0)
 599                {
 0600                    Debug.LogError($"Couldn't get any wearable for category {category} and bodyshape {model.bodyShape.id
 601                }
 602
 7603                var wearable = supportedWearables[Random.Range(0, supportedWearables.Length - 1)];
 7604                EquipWearable(wearable);
 605            }
 1606        }
 607
 1608        UpdateAvatarPreview(false);
 1609    }
 610
 611    private List<WearableItem> GetWearablesReplacedBy(WearableItem wearableItem)
 612    {
 1027613        var wearablesToReplace = new List<WearableItem>();
 1027614        var categoriesToReplace = new HashSet<string>(wearableItem.GetReplacesList(model.bodyShape.id) ?? new string[0])
 615
 1027616        int wearableCount = model.wearables.Count;
 7820617        for (int i = 0; i < wearableCount; i++)
 618        {
 2883619            var wearable = model.wearables[i];
 2883620            if (wearable == null) continue;
 621
 2883622            if (categoriesToReplace.Contains(wearable.data.category))
 623            {
 2624                wearablesToReplace.Add(wearable);
 2625            }
 626            else
 627            {
 628                //For retrocompatibility's sake we check current wearables against new one (compatibility matrix is symm
 2881629                HashSet<string> replacesList = new HashSet<string>(wearable.GetReplacesList(model.bodyShape.id) ?? new s
 2881630                if (replacesList.Contains(wearableItem.data.category))
 631                {
 0632                    wearablesToReplace.Add(wearable);
 633                }
 634            }
 635        }
 636
 1027637        return wearablesToReplace;
 638    }
 639
 88640    public void SetVisibility(bool visible) { avatarEditorVisible.Set(visible); }
 641
 184642    private void OnAvatarEditorVisibleChanged(bool current, bool previous) { SetVisibility_Internal(current); }
 643
 644    public void SetVisibility_Internal(bool visible)
 645    {
 92646        if (!visible && view.isOpen)
 647        {
 1648            view.ResetPreviewEmote();
 649
 1650            if (DataStore.i.common.isSignUpFlow.Get())
 0651                DataStore.i.virtualAudioMixer.sceneSFXVolume.Set(1f);
 652
 1653            Environment.i.messaging.manager.paused = false;
 1654            DataStore.i.skyboxConfig.avatarMatProfile.Set(AvatarMaterialProfile.InWorld);
 1655            if (prevMouseLockState && DataStore.i.common.isSignUpFlow.Get())
 656            {
 0657                Utils.LockCursor();
 658            }
 659
 660            // NOTE(Brian): SSAO doesn't work correctly with the offseted avatar preview if the renderScale != 1.0
 1661            var asset = GraphicsSettings.renderPipelineAsset as UniversalRenderPipelineAsset;
 1662            asset.renderScale = prevRenderScale;
 663
 1664            if (DataStore.i.common.isSignUpFlow.Get())
 0665                CommonScriptableObjects.isFullscreenHUDOpen.Set(false);
 666
 1667            DataStore.i.common.isPlayerRendererLoaded.OnChange -= PlayerRendererLoaded;
 668
 1669            OnClose?.Invoke();
 0670        }
 91671        else if (visible && !view.isOpen)
 672        {
 43673            if (DataStore.i.common.isSignUpFlow.Get())
 674            {
 0675                DataStore.i.virtualAudioMixer.sceneSFXVolume.Set(0f);
 0676                view.sectionSelector.Hide(true);
 0677            }
 678            else
 679            {
 43680                view.sectionSelector.Show(true);
 681            }
 682
 43683            LoadOwnedWereables(userProfile);
 43684            LoadCollections();
 43685            Environment.i.messaging.manager.paused = DataStore.i.common.isSignUpFlow.Get();
 43686            DataStore.i.skyboxConfig.avatarMatProfile.Set(AvatarMaterialProfile.InEditor);
 687
 43688            prevMouseLockState = Utils.IsCursorLocked;
 689
 43690            if (DataStore.i.common.isSignUpFlow.Get() || !DataStore.i.exploreV2.isInitialized.Get())
 43691                Utils.UnlockCursor();
 692
 693            // NOTE(Brian): SSAO doesn't work correctly with the offseted avatar preview if the renderScale != 1.0
 43694            var asset = GraphicsSettings.renderPipelineAsset as UniversalRenderPipelineAsset;
 43695            prevRenderScale = asset.renderScale;
 43696            asset.renderScale = 1.0f;
 697
 43698            if (DataStore.i.common.isSignUpFlow.Get())
 0699                CommonScriptableObjects.isFullscreenHUDOpen.Set(true);
 700
 43701            DataStore.i.common.isPlayerRendererLoaded.OnChange += PlayerRendererLoaded;
 702
 43703            OnOpen?.Invoke();
 704        }
 705
 92706        view.SetVisibility(visible);
 92707    }
 708
 709    public void Dispose()
 710    {
 43711        avatarEditorVisible.OnChange -= OnAvatarEditorVisibleChanged;
 43712        configureBackpackInFullscreenMenu.OnChange -= ConfigureBackpackInFullscreenMenuChanged;
 43713        DataStore.i.common.isPlayerRendererLoaded.OnChange -= PlayerRendererLoaded;
 43714        exploreV2IsOpen.OnChange -= ExploreV2IsOpenChanged;
 43715        emotesCustomizationDataStore.isEmotesCustomizationSelected.OnChange -= HandleEmotesCostumizationSelection;
 43716        emotesCustomizationDataStore.currentLoadedEmotes.OnAdded -= OnNewEmoteAdded;
 717
 43718        emotesCustomizationComponentController.onEmotePreviewed -= OnPreviewEmote;
 43719        emotesCustomizationComponentController.onEmoteEquipped -= OnEmoteEquipped;
 43720        emotesCustomizationComponentController.onEmoteUnequipped -= OnEmoteUnequipped;
 43721        emotesCustomizationComponentController.onEmoteSell -= OnRedirectToEmoteSelling;
 722
 43723        CleanUp();
 43724    }
 725
 726    public void CleanUp()
 727    {
 48728        UnequipAllWearables();
 729
 48730        if (view != null)
 48731            view.CleanUp();
 732
 48733        this.userProfile.OnUpdate -= LoadUserProfile;
 48734        this.catalog.OnAdded -= AddWearable;
 48735        this.catalog.OnRemoved -= RemoveWearable;
 48736        DataStore.i.common.isPlayerRendererLoaded.OnChange -= PlayerRendererLoaded;
 48737    }
 738
 0739    public void SetConfiguration(HUDConfiguration configuration) { SetVisibility(configuration.active); }
 740
 741    public void SaveAvatar(Texture2D face256Snapshot, Texture2D bodySnapshot)
 742    {
 1743        var avatarModel = model.ToAvatarModel();
 744
 1745        SendNewEquippedWearablesAnalytics(userProfile.avatar.wearables, avatarModel.wearables);
 746
 1747        WebInterface.SendSaveAvatar(avatarModel, face256Snapshot, bodySnapshot, DataStore.i.common.isSignUpFlow.Get());
 1748        userProfile.OverrideAvatar(avatarModel, face256Snapshot);
 1749        if (DataStore.i.common.isSignUpFlow.Get())
 0750            DataStore.i.HUDs.signupVisible.Set(true);
 751
 1752        emotesCustomizationDataStore.equippedEmotes.Set(emotesCustomizationDataStore.unsavedEquippedEmotes.Get());
 1753        avatarIsDirty = false;
 1754        SetVisibility(false);
 1755    }
 756
 757    public void GoToMarketplaceOrConnectWallet()
 758    {
 0759        if (userProfile.hasConnectedWeb3)
 0760            WebInterface.OpenURL(URL_MARKET_PLACE);
 761        else
 0762            WebInterface.OpenURL(URL_GET_A_WALLET);
 0763    }
 764
 765    public void SellCollectible(string collectibleId)
 766    {
 0767        var ownedCollectible = ownedNftCollectionsL1.FirstOrDefault(nft => nft.urn == collectibleId);
 0768        if (ownedCollectible == null)
 0769            ownedCollectible = ownedNftCollectionsL2.FirstOrDefault(nft => nft.urn == collectibleId);
 770
 0771        if (ownedCollectible != null)
 0772            WebInterface.OpenURL(URL_SELL_SPECIFIC_COLLECTIBLE.Replace("{collectionId}", ownedCollectible.collectionId).
 773        else
 0774            WebInterface.OpenURL(URL_SELL_COLLECTIBLE_GENERIC);
 0775    }
 776
 0777    public void ToggleVisibility() { SetVisibility(!view.isOpen); }
 778
 96779    private void ConfigureBackpackInFullscreenMenuChanged(Transform currentParentTransform, Transform previousParentTran
 780
 781    private void ExploreV2IsOpenChanged(bool current, bool previous)
 782    {
 0783        if (!current && avatarIsDirty)
 784        {
 0785            LoadUserProfile(userProfile, true);
 786
 0787            emotesCustomizationComponentController.RestoreEmoteSlots();
 788
 0789            avatarIsDirty = false;
 790        }
 0791    }
 792
 793    private void LoadCollections()
 794    {
 43795        if (!isThirdPartyCollectionsEnabled || collectionsAlreadyLoaded)
 43796            return;
 797
 0798        WearablesFetchingHelper.GetThirdPartyCollections()
 799            .Then((collections) =>
 800            {
 0801                view.LoadCollectionsDropdown(collections);
 0802                collectionsAlreadyLoaded = true;
 0803                LoadUserThirdPartyWearables();
 0804            })
 0805            .Catch((error) => Debug.LogError(error));
 0806    }
 807
 808    private void LoadUserThirdPartyWearables()
 809    {
 0810        List<string> collectionIdsToLoad = new List<string>();
 0811        foreach (string wearableId in userProfile.avatar.wearables)
 812        {
 0813            CatalogController.wearableCatalog.TryGetValue(wearableId, out var wearable);
 814
 0815            if (wearable != null && wearable.IsFromThirdPartyCollection)
 816            {
 0817                if (!collectionIdsToLoad.Contains(wearable.ThirdPartyCollectionId))
 0818                    collectionIdsToLoad.Add(wearable.ThirdPartyCollectionId);
 819            }
 820        }
 821
 0822        foreach (string collectionId in collectionIdsToLoad)
 823        {
 0824            view.ToggleThirdPartyCollection(collectionId, true);
 825        }
 0826    }
 827
 828    public void ToggleThirdPartyCollection(bool isOn, string collectionId, string collectionName)
 829    {
 1830        if (isOn)
 1831            FetchAndShowThirdPartyCollection(collectionId, collectionName);
 832        else
 0833            RemoveThirdPartyCollection(collectionId);
 0834    }
 835
 836    private void FetchAndShowThirdPartyCollection(string collectionId, string collectionName)
 837    {
 1838        view.BlockCollectionsDropdown(true);
 1839        CatalogController.RequestThirdPartyWearablesByCollection(userProfile.userId, collectionId)
 840            .Then(wearables =>
 841            {
 0842                if (wearables.Count().Equals(0)) view.ShowNoItemOfWearableCollectionWarning();
 843
 0844                foreach (var wearable in wearables)
 845                {
 0846                    if (!userProfile.ContainsInInventory(wearable.id))
 847                    {
 0848                        userProfile.AddToInventory(wearable.id);
 849
 0850                        if (!thirdPartyWearablesLoaded.Contains(wearable.id))
 0851                            thirdPartyWearablesLoaded.Add(wearable.id);
 852                    }
 853                }
 854
 0855                view.BlockCollectionsDropdown(false);
 0856                LoadUserProfile(userProfile, true);
 0857            })
 858            .Catch((error) =>
 859            {
 0860                view.BlockCollectionsDropdown(false);
 0861                Debug.LogError(error);
 0862            });
 1863    }
 864
 865    private void RemoveThirdPartyCollection(string collectionId)
 866    {
 0867        var wearablesToRemove = CatalogController.i.Wearables.GetValues()
 0868            .Where(wearable => !userProfile.HasEquipped(wearable.id)
 869                               && wearable.ThirdPartyCollectionId == collectionId)
 0870            .Select(item => item.id)
 871            .ToList();
 0872        CatalogController.i.Remove(wearablesToRemove);
 873
 0874        foreach (string wearableId in wearablesToRemove)
 875        {
 0876            userProfile.RemoveFromInventory(wearableId);
 0877            thirdPartyWearablesLoaded.Remove(wearableId);
 878        }
 879
 0880        LoadUserProfile(userProfile, true);
 0881    }
 882
 883    private bool ShouldShowHideOtherWearablesToast(WearableItem wearable)
 884    {
 0885        var isWearingSkinAlready = model.wearables.Any(item => item.IsSkin());
 0886        return wearable.IsSkin() && !isWearingSkinAlready;
 887    }
 888
 889    private bool IsTryingToReplaceSkin(WearableItem wearable)
 890    {
 12891        return model.wearables.Any(skin =>
 892        {
 80893            return skin.IsSkin()
 894                   && skin.DoesHide(wearable.data.category, model.bodyShape.id);
 895        });
 896    }
 897
 898    private bool ShouldShowReplaceOtherWearablesToast(WearableItem wearable)
 899    {
 0900        if (IsTryingToReplaceSkin(wearable)) return true;
 0901        var toReplace = GetWearablesReplacedBy(wearable);
 0902        if (wearable == null || toReplace.Count == 0) return false;
 0903        if (model.wearables.Contains(wearable)) return false;
 904
 905        // NOTE: why just 1?
 0906        if (toReplace.Count == 1)
 907        {
 0908            var w = toReplace[0];
 0909            if (w.data.category == wearable.data.category)
 0910                return false;
 911        }
 0912        return true;
 913    }
 914
 915
 916    private void HandleEmotesCostumizationSelection(bool current, bool previous)
 917    {
 0918        if (!current)
 0919            return;
 920
 0921        view.sectionSelector.GetSection(AvatarEditorHUDView.EMOTES_SECTION_INDEX).SelectToggle();
 0922    }
 923
 924    private void OnNewEmoteAdded(string emoteId)
 925    {
 0926        if (!isAvatarPreviewReady)
 0927            return;
 928
 0929        UpdateAvatarPreview(true);
 0930    }
 931
 0932    private void OnPreviewEmote(string emoteId) { view.PlayPreviewEmote(emoteId); }
 933
 934    private void OnEmoteEquipped(string emoteId)
 935    {
 0936        catalog.TryGetValue(emoteId, out WearableItem equippedEmote);
 937
 0938        if (equippedEmote != null)
 0939            EquipEmote(equippedEmote);
 0940    }
 941
 942    private void OnEmoteUnequipped(string emoteId)
 943    {
 0944        catalog.TryGetValue(emoteId, out WearableItem unequippedEmote);
 945
 0946        if (unequippedEmote != null)
 0947            UnequipEmote(unequippedEmote);
 0948    }
 949
 0950    private void OnRedirectToEmoteSelling(string emoteId) { SellCollectible(emoteId); }
 951
 952    internal void SendNewEquippedWearablesAnalytics(List<string> oldWearables, List<string> newWearables)
 953    {
 28954        for (int i = 0; i < newWearables.Count; i++)
 955        {
 12956            if (oldWearables.Contains(newWearables[i]))
 957                continue;
 958
 9959            catalog.TryGetValue(newWearables[i], out WearableItem newEquippedEmote);
 9960            if (newEquippedEmote != null && !newEquippedEmote.IsEmote())
 9961                SendEquipWearableAnalytic(newEquippedEmote);
 962        }
 2963    }
 964
 965    private void SendEquipWearableAnalytic(WearableItem equippedWearable)
 966    {
 9967        Dictionary<string, string> data = new Dictionary<string, string>();
 9968        data.Add("name", equippedWearable.GetName());
 9969        data.Add("rarity", equippedWearable.rarity);
 9970        data.Add("category", equippedWearable.data.category);
 9971        data.Add("linked_wearable", equippedWearable.IsFromThirdPartyCollection.ToString());
 9972        data.Add("third_party_collection_id", equippedWearable.ThirdPartyCollectionId);
 9973        data.Add("is_in_l2", equippedWearable.IsInL2().ToString());
 9974        data.Add("smart_item", equippedWearable.IsSmart().ToString());
 975
 9976        analytics.SendAnalytic(EQUIP_WEARABLE_METRIC, data);
 9977    }
 978
 48979    internal virtual IEmotesCustomizationComponentController CreateEmotesController() => new EmotesCustomizationComponen
 980}

Methods/Properties

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