private void UpdateUI(LoadedAvatar avatar)
        {
            SetInteractableRecursively(avatar != null);
            UpdateCalibrationButtons(avatar);

            if (avatar == null)
            {
                _clearButton.interactable                 = false;
                _calibrateButton.interactable             = false;
                _automaticCalibrationSetting.interactable = false;
                _automaticCalibrationHoverHint.text       = "No avatar selected";

                return;
            }

            _currentAvatarSettings          = _settings.GetAvatarSettings(avatar.fileName);
            _currentAvatarManualCalibration = _calibrationData.GetAvatarManualCalibration(avatar.fileName);

            _ignoreExclusionsSetting.Value = _currentAvatarSettings.ignoreExclusions;

            _bypassCalibration.Value = _currentAvatarSettings.bypassCalibration;

            _automaticCalibrationSetting.Value        = _currentAvatarSettings.useAutomaticCalibration;
            _automaticCalibrationSetting.interactable = avatar.descriptor.supportsAutomaticCalibration;
            _automaticCalibrationHoverHint.text       = avatar.descriptor.supportsAutomaticCalibration ? "Use automatic calibration instead of manual calibration." : "Not supported by current avatar";
        }
        public static async Task <string> RecalculateHashForAvatar(LoadedAvatar info)
        {
            FileInfo file = new FileInfo(Path.Combine(Path.GetFullPath("CustomAvatars"), info.fullPath));

            Plugin.log.Info($"Calculating hash for avatar \"{info.descriptor.name}\"...");

            var newHash = await CalculateHash(Path.Combine(Path.GetFullPath("CustomAvatars"), info.fullPath));

            HashData data = new HashData()
            {
                lastWriteTime = file.LastWriteTimeUtc, hash = newHash
            };

            if (_hashes.ContainsKey(info.fullPath))
            {
                _hashes[info.fullPath] = data;
            }
            else
            {
                _hashes.Add(info.fullPath, data);
            }

            Save();

            return(newHash);
        }
        private void UpdateUI(LoadedAvatar avatar)
        {
            DisableCalibrationMode(false);

            if (avatar == null)
            {
                _clearButton.interactable     = false;
                _calibrateButton.interactable = false;
                _automaticCalibrationSetting.checkbox.interactable = false;
                _automaticCalibrationHoverHint.text = "No avatar selected";

                return;
            }

            _currentAvatarSettings          = _settings.GetAvatarSettings(avatar.fileName);
            _currentAvatarManualCalibration = _calibrationData.GetAvatarManualCalibration(avatar.fileName);

            UpdateCalibrationButtons(avatar);

            _ignoreExclusionsSetting.CheckboxValue = _currentAvatarSettings.ignoreExclusions;

            _bypassCalibration.CheckboxValue         = _currentAvatarSettings.bypassCalibration;
            _bypassCalibration.checkbox.interactable = avatar.supportsFullBodyTracking;
            _bypassCalibrationHoverHint.text         = avatar.supportsFullBodyTracking ? "Disable the need for calibration before full body tracking is applied." : "Not supported by current avatar";

            _automaticCalibrationSetting.CheckboxValue         = _currentAvatarSettings.useAutomaticCalibration;
            _automaticCalibrationSetting.checkbox.interactable = avatar.descriptor.supportsAutomaticCalibration;
            _automaticCalibrationHoverHint.text = avatar.descriptor.supportsAutomaticCalibration ? "Use automatic calibration instead of manual calibration." : "Not supported by current avatar";
        }
 internal AvatarListItem(LoadedAvatar avatar)
 {
     name        = avatar.descriptor.name;
     author      = avatar.descriptor.author;
     icon        = avatar.descriptor.cover?.texture;
     this.avatar = avatar;
 }
Пример #5
0
        public void GetAvatarsAsync(Action <LoadedAvatar> success, Action <Exception> error)
        {
            Plugin.logger.Info("Loading all avatars from " + kCustomAvatarsPath);

            foreach (string fileName in GetAvatarFileNames())
            {
                SharedCoroutineStarter.instance.StartCoroutine(LoadedAvatar.FromFileCoroutine(fileName, success, error));
            }
        }
Пример #6
0
 public void SwitchToAvatarAsync(string filePath)
 {
     SharedCoroutineStarter.instance.StartCoroutine(LoadedAvatar.FromFileCoroutine(filePath, avatar =>
     {
         Plugin.logger.Info("Successfully loaded avatar " + avatar.descriptor.name);
         SwitchToAvatar(avatar);
     }, ex =>
     {
         Plugin.logger.Error("Failed to load avatar: " + ex.Message);
     }));
 }
        private void CreateAvatar(LoadedAvatar avatar)
        {
            loadedAvatar = avatar;
            if (spawnedAvatar != null)
            {
                Destroy(spawnedAvatar);
            }

            spawnedAvatar = _avatarSpawner.SpawnAvatar(avatar, new MultiplayerAvatarInput(poseController, transform.name != "MultiplayerLobbyAvatar(Clone)"), poseController.transform);
            spawnedAvatar.SetLocomotionEnabled(true);
            spawnedAvatar.scale = avatarData.scale;
        }
Пример #8
0
        private void UpdateCalibrationButtons(LoadedAvatar avatar)
        {
            if (!_playerInput.TryGetUncalibratedPose(DeviceUse.Waist, out Pose _) &&
                !_playerInput.TryGetUncalibratedPose(DeviceUse.LeftFoot, out Pose _) &&
                !_playerInput.TryGetUncalibratedPose(DeviceUse.RightFoot, out Pose _))
            {
                _autoCalibrateButton.interactable  = false;
                _autoClearButton.interactable      = _calibrationData.automaticCalibration.isCalibrated;
                _autoCalibrateButtonHoverHint.text = "No trackers detected";

                _calibrateButton.interactable  = false;
                _clearButton.interactable      = _currentAvatarManualCalibration?.isCalibrated == true;
                _calibrateButtonHoverHint.text = "No trackers detected";
                _calibrateButtonText.text      = "Calibrate";
                _clearButtonText.text          = "Clear";

                return;
            }

            bool isManualCalibrationPossible    = avatar != null && avatar.supportsFullBodyTracking;
            bool isAutomaticCalibrationPossible = isManualCalibrationPossible && avatar.descriptor.supportsAutomaticCalibration;

            if (isAutomaticCalibrationPossible)
            {
                _autoCalibrateButton.interactable  = true;
                _autoClearButton.interactable      = _calibrationData.automaticCalibration.isCalibrated;
                _autoCalibrateButtonHoverHint.text = "Calibrate full body tracking automatically";
            }
            else
            {
                _autoCalibrateButton.interactable  = false;
                _autoClearButton.interactable      = false;
                _autoCalibrateButtonHoverHint.text = "Not supported by current avatar";
            }

            if (isManualCalibrationPossible)
            {
                _calibrateButton.interactable  = true;
                _clearButton.interactable      = _calibrating || _currentAvatarManualCalibration?.isCalibrated == true;
                _calibrateButtonHoverHint.text = "Start manual full body calibration";
                _calibrateButtonText.text      = _calibrating ? "Save" : "Calibrate";
                _clearButtonText.text          = _calibrating ? "Cancel" : "Clear";
            }
            else
            {
                _calibrateButton.interactable  = false;
                _clearButton.interactable      = false;
                _calibrateButtonHoverHint.text = "Not supported by current avatar";
                _calibrateButtonText.text      = "Calibrate";
                _clearButtonText.text          = "Clear";
            }
        }
Пример #9
0
        private static SpawnedAvatar SpawnAvatar(LoadedAvatar customAvatar, AvatarInput input)
        {
            if (customAvatar == null)
            {
                throw new ArgumentNullException(nameof(customAvatar));
            }
            if (input == null)
            {
                throw new ArgumentNullException(nameof(input));
            }

            var spawnedAvatar = new SpawnedAvatar(customAvatar, input);

            return(spawnedAvatar);
        }
        public static async Task <string> GetHashForAvatar(LoadedAvatar info, bool save = true)
        {
            if (_hashes.TryGetValue(info.fullPath, out var data))
            {
                FileInfo file = new FileInfo(Path.Combine(Path.GetFullPath("CustomAvatars"), info.fullPath));

                if (file.LastWriteTimeUtc != data.lastWriteTime)
                {
                    Plugin.log.Info($"Calculating hash for avatar \"{info.descriptor.name}\"...");

                    var newHash = await CalculateHash(Path.Combine(Path.GetFullPath("CustomAvatars"), info.fullPath));

                    data.lastWriteTime     = file.LastWriteTimeUtc;
                    data.hash              = newHash;
                    _hashes[info.fullPath] = data;

                    if (save)
                    {
                        Save();
                    }

                    return(newHash);
                }
                else
                {
                    return(data.hash);
                }
            }
            else
            {
                FileInfo file = new FileInfo(Path.Combine(Path.GetFullPath("CustomAvatars"), info.fullPath));

                Plugin.log.Info($"Calculating hash for avatar \"{info.descriptor.name}\"...");

                var newHash = await CalculateHash(Path.Combine(Path.GetFullPath("CustomAvatars"), info.fullPath));

                data.lastWriteTime     = file.LastWriteTimeUtc;
                data.hash              = newHash;
                _hashes[info.fullPath] = data;

                if (save)
                {
                    Save();
                }

                return(newHash);
            }
        }
        internal VRPlayerInput(TrackedDeviceManager trackedDeviceManager, LoadedAvatar avatar, Settings settings, CalibrationData calibrationData)
        {
            _deviceManager     = trackedDeviceManager;
            _settings          = settings;
            _avatarSettings    = settings.GetAvatarSettings(avatar.fileName);
            _calibrationData   = calibrationData;
            _manualCalibration = calibrationData.GetAvatarManualCalibration(avatar.fileName);

            _deviceManager.deviceAdded            += OnDevicesUpdated;
            _deviceManager.deviceRemoved          += OnDevicesUpdated;
            _deviceManager.deviceTrackingAcquired += OnDevicesUpdated;
            _deviceManager.deviceTrackingLost     += OnDevicesUpdated;

            _leftHandAnimAction  = new SkeletalInput("/actions/customavatars/in/lefthandanim");
            _rightHandAnimAction = new SkeletalInput("/actions/customavatars/in/righthandanim");
        }
        private void UpdateCalibrationButtons(LoadedAvatar avatar)
        {
            if (!_trackedDeviceManager.waist.tracked && !_trackedDeviceManager.leftFoot.tracked && !_trackedDeviceManager.rightFoot.tracked)
            {
                _autoCalibrateButton.interactable  = false;
                _autoClearButton.interactable      = false;
                _autoCalibrateButtonHoverHint.text = "No trackers detected";

                _calibrateButton.interactable  = false;
                _clearButton.interactable      = false;
                _calibrateButtonHoverHint.text = "No trackers detected";

                return;
            }

            bool isManualCalibrationPossible    = avatar != null && avatar.isIKAvatar && avatar.supportsFullBodyTracking;
            bool isAutomaticCalibrationPossible = isManualCalibrationPossible && avatar.descriptor.supportsAutomaticCalibration;

            if (isAutomaticCalibrationPossible)
            {
                _autoCalibrateButton.interactable  = true;
                _autoClearButton.interactable      = _currentAvatarManualCalibration.isCalibrated;
                _autoCalibrateButtonHoverHint.text = "Calibrate full body tracking automatically";
            }
            else
            {
                _autoCalibrateButton.interactable  = false;
                _autoClearButton.interactable      = false;
                _autoCalibrateButtonHoverHint.text = "Not supported by current avatar";
            }

            if (isManualCalibrationPossible)
            {
                _calibrateButton.interactable  = true;
                _clearButton.interactable      = _currentAvatarManualCalibration.isCalibrated;
                _calibrateButtonHoverHint.text = "Start manual full body calibration";
            }
            else
            {
                _calibrateButton.interactable  = false;
                _clearButton.interactable      = false;
                _calibrateButtonHoverHint.text = "Not supported by current avatar";
            }
        }
Пример #13
0
        private void SwitchToAvatar(LoadedAvatar avatar)
        {
            if ((currentlySpawnedAvatar && currentlySpawnedAvatar.avatar == avatar) || avatar?.fullPath != _switchingToPath)
            {
                avatar?.Dispose();
                return;
            }

            if (avatar == null)
            {
                _logger.Info("No avatar selected");
                avatarChanged?.Invoke(null);
                _settings.previousAvatarPath = null;
                UpdateFloorOffsetForCurrentAvatar();
                return;
            }

            var avatarInfo = new AvatarInfo(avatar);

            _settings.previousAvatarPath = avatarInfo.fileName;

            // cache avatar info since loading asset bundles is expensive
            if (_avatarInfos.ContainsKey(avatarInfo.fileName))
            {
                _avatarInfos[avatarInfo.fileName] = avatarInfo;
            }
            else
            {
                _avatarInfos.Add(avatarInfo.fileName, avatarInfo);
            }

            currentlySpawnedAvatar = _spawner.SpawnAvatar(avatar, _container.Resolve <VRPlayerInput>(), _avatarContainer.transform);
            _currentAvatarSettings = _settings.GetAvatarSettings(avatar.fileName);

            ResizeCurrentAvatar();
            UpdateFirstPersonVisibility();
            UpdateLocomotionEnabled();

            avatarChanged?.Invoke(currentlySpawnedAvatar);
        }
Пример #14
0
        public static void LoadAvatars()
        {
            if (defaultAvatarInstance == null)
            {
                if (Config.Instance.DownloadAvatars)
                {
                    defaultAvatarInstance = ModelSaberAPI.cachedAvatars.FirstOrDefault(x => x.Value.fullPath.ToLower().Contains("loading.avatar")).Value;

                    if (defaultAvatarInstance == null)//fallback to multiplayer avatar
                    {
                        defaultAvatarInstance = ModelSaberAPI.cachedAvatars.FirstOrDefault(x => x.Value.fullPath.ToLower().Contains("multiplayer.avatar")).Value;
                    }

                    if (defaultAvatarInstance == null)//fallback to default avatar
                    {
                        defaultAvatarInstance = ModelSaberAPI.cachedAvatars.FirstOrDefault(x => x.Value.fullPath.ToLower().Contains("templatefullbody.avatar")).Value;
                    }

                    if (defaultAvatarInstance == null)//fallback to ANY avatar
                    {
                        defaultAvatarInstance = ModelSaberAPI.cachedAvatars.FirstOrDefault().Value;
                    }
                }
                else
                {
                    defaultAvatarInstance = ModelSaberAPI.cachedAvatars.FirstOrDefault(x => x.Value.fullPath.ToLower().Contains("multiplayer.avatar")).Value;

                    if (defaultAvatarInstance == null)//fallback to default avatar
                    {
                        defaultAvatarInstance = ModelSaberAPI.cachedAvatars.FirstOrDefault(x => x.Value.fullPath.ToLower().Contains("templatefullbody.avatar")).Value;
                    }

                    if (defaultAvatarInstance == null)//fallback to ANY avatar
                    {
                        defaultAvatarInstance = ModelSaberAPI.cachedAvatars.FirstOrDefault().Value;
                    }
                }
            }
        }
Пример #15
0
        public void SwitchToAvatar(LoadedAvatar avatar)
        {
            if (currentlySpawnedAvatar?.customAvatar == avatar)
            {
                return;
            }

            currentlySpawnedAvatar?.Destroy();
            currentlySpawnedAvatar = null;

            SettingsManager.settings.previousAvatarPath = avatar?.fullPath;

            if (avatar == null)
            {
                return;
            }

            currentlySpawnedAvatar = SpawnAvatar(avatar, new VRAvatarInput());

            avatarChanged?.Invoke(currentlySpawnedAvatar);

            ResizeCurrentAvatar();
            currentlySpawnedAvatar?.OnFirstPersonEnabledChanged();
        }
Пример #16
0
        public void SetPlayerInfo(PlayerInfo _playerInfo, float offset, bool isLocal)
        {
            if (_playerInfo == default)
            {
                if (playerNameText != null)
                {
                    playerNameText.gameObject.SetActive(false);
                }
                if (playerSpeakerIcon != null)
                {
                    playerSpeakerIcon.gameObject.SetActive(false);
                }
                if (avatar != null && avatar.eventsPlayer != null)
                {
                    avatar.Destroy();
                    avatar = null;
                }
                return;
            }

            try
            {
                playerInfo       = _playerInfo.updateInfo;
                playerId         = _playerInfo.playerId;
                playerAvatarHash = _playerInfo.avatarHash;
                playerName       = _playerInfo.playerName;

                if (playerNameText != null && playerSpeakerIcon != null)
                {
                    if (isLocal)
                    {
                        playerNameText.gameObject.SetActive(false);
                        playerSpeakerIcon.gameObject.SetActive(false);
#if !DEBUG
                        if (avatar != null && avatar.eventsPlayer != null)
                        {
                            avatar.Destroy();
                        }
#endif
                    }
                    else
                    {
                        playerNameText.gameObject.SetActive(true);
                        playerNameText.alignment = TextAlignmentOptions.Center;
                        playerSpeakerIcon.gameObject.SetActive(InGameOnlineController.Instance.VoiceChatIsTalking(playerId));
                    }
                }
                else
                {
                    return;
                }
#if !DEBUG
                if ((avatar == null || currentAvatarHash != playerAvatarHash) && !isLocal)
#else
                if ((avatar == null || currentAvatarHash != playerAvatarHash))
#endif
                {
                    if (unableToSpawnAvatar)
                    {
                        if (retryTimeCounter + 5f > Time.realtimeSinceStartup)
                        {
                            retryTimeCounter    = -1f;
                            unableToSpawnAvatar = false;
                        }
                    }
                    else
                    {
                        if (ModelSaberAPI.cachedAvatars.ContainsKey(playerAvatarHash))
                        {
                            LoadedAvatar cachedAvatar = ModelSaberAPI.cachedAvatars[playerAvatarHash];

                            if (cachedAvatar != null)
                            {
                                if (avatar != null && avatar.eventsPlayer != null)
                                {
                                    avatar.Destroy();
                                    avatar = null;
                                }

                                avatar = AvatarManager.SpawnAvatar(cachedAvatar, avatarInput);
                                avatar.SetChildrenToLayer(10);

                                currentAvatarHash = playerAvatarHash;
                            }
                            else
                            {
                                unableToSpawnAvatar = true;
                                retryTimeCounter    = Time.realtimeSinceStartup;
                            }
                        }
                        else
                        {
                            if (Config.Instance.DownloadAvatars)
                            {
                                if (!Config.Instance.DownloadNSFWAvatars && ModelSaberAPI.nsfwAvatars.Contains(playerAvatarHash))
                                {
                                    unableToSpawnAvatar = true;
                                    retryTimeCounter    = Time.realtimeSinceStartup;
                                }
                                else
                                {
                                    ModelSaberAPI.avatarDownloaded -= AvatarDownloaded;
                                    ModelSaberAPI.avatarDownloaded += AvatarDownloaded;

                                    if (!ModelSaberAPI.queuedAvatars.Contains(playerAvatarHash))
                                    {
                                        SharedCoroutineStarter.instance.StartCoroutine(ModelSaberAPI.DownloadAvatarCoroutine(playerAvatarHash));

                                        if (avatar != null && avatar.eventsPlayer != null)
                                        {
                                            avatar.Destroy();
                                        }

                                        avatar = AvatarManager.SpawnAvatar(defaultAvatarInstance, avatarInput);
                                        avatar.SetChildrenToLayer(10);
                                    }
                                }
                            }
                        }
                    }
                }

                Vector3 offsetVector = new Vector3(offset, 0f, 0f);

                avatarInput.headPos      = playerInfo.headPos + offsetVector;
                avatarInput.rightHandPos = playerInfo.rightHandPos + offsetVector;
                avatarInput.leftHandPos  = playerInfo.leftHandPos + offsetVector;

                avatarInput.headRot      = playerInfo.headRot;
                avatarInput.rightHandRot = playerInfo.rightHandRot;
                avatarInput.leftHandRot  = playerInfo.leftHandRot;

                avatarInput.poseValid = true;

                avatarInput.fullBodyTracking = playerInfo.fullBodyTracking;
                if (playerInfo.fullBodyTracking)
                {
                    avatarInput.rightLegPos = playerInfo.rightLegPos + offsetVector;
                    avatarInput.leftLegPos  = playerInfo.leftLegPos + offsetVector;
                    avatarInput.pelvisPos   = playerInfo.pelvisPos + offsetVector;
                    avatarInput.rightLegRot = playerInfo.rightLegRot;
                    avatarInput.leftLegRot  = playerInfo.leftLegRot;
                    avatarInput.pelvisRot   = playerInfo.pelvisRot;
                }

                transform.position = avatarInput.headPos;

                playerNameText.text = playerName;

                if (playerInfo.playerFlags.rainbowName && !rainbowName)
                {
                    playerNameText.color = playerInfo.playerNameColor;
                    nameColor            = HSBColor.FromColor(playerInfo.playerNameColor);
                }
                else if (!playerInfo.playerFlags.rainbowName && playerNameText.color != playerInfo.playerNameColor)
                {
                    playerNameText.color = playerInfo.playerNameColor;
                }

                rainbowName = playerInfo.playerFlags.rainbowName;
            }
            catch (Exception e)
            {
                Plugin.log.Critical(e);
            }
        }
Пример #17
0
        public static IEnumerator DownloadAvatarCoroutine(string hash)
        {
            queuedAvatars.Add(hash);
            string          downloadUrl = "";
            string          avatarName  = "";
            UnityWebRequest www         = SongDownloader.GetRequestForUrl("https://modelsaber.com/api/v1/avatar/get.php?filter=hash:" + hash);

            www.timeout = 10;

            yield return(www.SendWebRequest());

            if (www.isNetworkError || www.isHttpError)
            {
                Plugin.log.Error($"Unable to download avatar! {(www.isNetworkError ? $"Network error: " + www.error : (www.isHttpError ? $"HTTP error: " + www.error : "Unknown error"))}");
                queuedAvatars.Remove(hash);
                yield break;
            }
            else
            {
                Plugin.log.Debug("Received response from ModelSaber...");
                JSONNode node = JSON.Parse(www.downloadHandler.text);

                if (node.Count == 0)
                {
                    Plugin.log.Error($"Avatar with hash {hash} doesn't exist on ModelSaber!");
                    cachedAvatars.Add(hash, null);
                    queuedAvatars.Remove(hash);
                    yield break;
                }

                downloadUrl = node[0]["download"].Value;
                avatarName  = downloadUrl.Substring(downloadUrl.LastIndexOf("/") + 1);
            }

            if (string.IsNullOrEmpty(downloadUrl))
            {
                queuedAvatars.Remove(hash);
                yield break;
            }


            bool  timeout = false;
            float time    = 0f;
            UnityWebRequestAsyncOperation asyncRequest;

            try
            {
                www         = SongDownloader.GetRequestForUrl(downloadUrl);
                www.timeout = 0;

                asyncRequest = www.SendWebRequest();
            }
            catch (Exception e)
            {
                Plugin.log.Error($"Unable to download avatar! Exception: {e}");
                queuedAvatars.Remove(hash);
                yield break;
            }

            while (!asyncRequest.isDone)
            {
                yield return(null);

                time += Time.deltaTime;

                if ((time >= 5f && asyncRequest.progress <= float.Epsilon))
                {
                    www.Abort();
                    timeout = true;
                    Plugin.log.Error("Connection timed out!");
                }
            }


            if (www.isNetworkError || www.isHttpError || timeout)
            {
                queuedAvatars.Remove(hash);
                Plugin.log.Error("Unable to download avatar! " + (www.isNetworkError ? $"Network error: {www.error}" : (www.isHttpError ? $"HTTP error: {www.error}" : "Unknown error")));
            }
            else
            {
                Plugin.log.Debug("Received response from ModelSaber...");
                string docPath          = "";
                string customAvatarPath = "";

                byte[] data = www.downloadHandler.data;

                try
                {
                    docPath          = Application.dataPath;
                    docPath          = docPath.Substring(0, docPath.Length - 5);
                    docPath          = docPath.Substring(0, docPath.LastIndexOf("/"));
                    customAvatarPath = docPath + "/CustomAvatars/" + avatarName;

                    Plugin.log.Debug($"Saving avatar to \"{customAvatarPath}\"...");

                    File.WriteAllBytes(customAvatarPath, data);

                    Plugin.log.Debug("Downloaded avatar!");
                    Plugin.log.Debug($"Loading avatar...");

                    SharedCoroutineStarter.instance.StartCoroutine(LoadedAvatar.FromFileCoroutine(avatarName,
                                                                                                  (LoadedAvatar avatar) =>
                    {
                        queuedAvatars.Remove(hash);
                        cachedAvatars.Add(hash, avatar);
                        avatarDownloaded?.Invoke(hash);
                    }, (Exception ex) =>
                    {
                        Plugin.log.Error($"Unable to load avatar! Exception: {ex}");
                        queuedAvatars.Remove(hash);
                    }));
                }
                catch (Exception e)
                {
                    Plugin.log.Critical(e);
                    queuedAvatars.Remove(hash);
                    yield break;
                }
            }
        }
        private void UpdateCalibrationButtons(LoadedAvatar avatar)
        {
            if (_playerInput.TryGetUncalibratedPose(DeviceUse.LeftHand, out Pose _) && _playerInput.TryGetUncalibratedPose(DeviceUse.RightHand, out Pose _))
            {
                _measureButton.interactable  = true;
                _measureButtonHoverHint.text = "For optimal results, hold your arms out to either side of your body and point the ends of the controllers outwards as far as possible (turn your hands if necessary).";
            }
            else
            {
                _measureButton.interactable  = false;
                _measureButtonHoverHint.text = "Controllers not detected";
            }

            if (avatar == null)
            {
                _calibrateButton.interactable  = false;
                _clearButton.interactable      = false;
                _calibrateButtonHoverHint.text = "No avatar selected";
                _calibrateButtonText.text      = "Calibrate";
                _clearButtonText.text          = "Clear";

                _autoCalibrateButton.interactable  = false;
                _autoClearButton.interactable      = false;
                _autoCalibrateButtonHoverHint.text = "No avatar selected";

                return;
            }

            if (!_playerInput.TryGetUncalibratedPose(DeviceUse.Waist, out Pose _) &&
                !_playerInput.TryGetUncalibratedPose(DeviceUse.LeftFoot, out Pose _) &&
                !_playerInput.TryGetUncalibratedPose(DeviceUse.RightFoot, out Pose _))
            {
                _autoCalibrateButton.interactable  = false;
                _autoClearButton.interactable      = _calibrationData.automaticCalibration.isCalibrated;
                _autoCalibrateButtonHoverHint.text = "No trackers detected";

                _calibrateButton.interactable  = false;
                _clearButton.interactable      = _currentAvatarManualCalibration?.isCalibrated == true;
                _calibrateButtonHoverHint.text = "No trackers detected";
                _calibrateButtonText.text      = "Calibrate";
                _clearButtonText.text          = "Clear";

                return;
            }

            _calibrateButton.interactable  = true;
            _clearButton.interactable      = _calibrating || _currentAvatarManualCalibration?.isCalibrated == true;
            _calibrateButtonHoverHint.text = "Start manual full body calibration";
            _calibrateButtonText.text      = _calibrating ? "Save" : "Calibrate";
            _clearButtonText.text          = _calibrating ? "Cancel" : "Clear";

            if (avatar.descriptor.supportsAutomaticCalibration)
            {
                _autoCalibrateButton.interactable  = true;
                _autoClearButton.interactable      = _calibrationData.automaticCalibration.isCalibrated;
                _autoCalibrateButtonHoverHint.text = "Calibrate full body tracking automatically";
            }
            else
            {
                _autoCalibrateButton.interactable  = false;
                _autoClearButton.interactable      = false;
                _autoCalibrateButtonHoverHint.text = "Not supported by current avatar";
            }
        }