コード例 #1
0
 public static void Traverse(GameObject obj, string history = null)
 {
     Logger.Info($"BRANCH: {history}/{obj.name}");
     foreach (Transform child in obj.transform)
     {
         Traverse(child.gameObject, history + $"/{obj.name}");
     }
 }
コード例 #2
0
 public static void PrintObjectHierarchy()
 {
     Logger.Success("BEGINNING TREE");
     foreach (GameObject obj in Object.FindObjectsOfType(typeof(GameObject)))
     {
         if (obj.transform.parent == null)
         {
             Logger.Warning($"NEW TREE ROOT: {obj.name}");
             Traverse(obj);
         }
     }
 }
コード例 #3
0
ファイル: Client.cs プロジェクト: MatrikMoon/EventSystem
        public static void a(ulong userId, string levelId, int difficultyLevel, bool fullCombo, int score, string signed, int playerOptions, int gameOptions, Action <bool> scoreUploadedCallback = null)
#endif
        {
            //Build score object
            Score s = new Score(userId.ToString(), levelId, score, difficultyLevel, fullCombo, playerOptions, gameOptions, characteristic, signed);

            Logger.Info($"Trying to submit score: {levelId} {userId} {difficultyLevel} {score} {fullCombo} {signed}");

            JSONObject o = new JSONObject();

            o.Add("pb", new JSONString(s.ToBase64()));

            SharedCoroutineStarter.instance.StartCoroutine(PostCoroutine(o.ToString(), $"{discordCommunityApi}/submit/", scoreUploadedCallback));
        }
コード例 #4
0
ファイル: Client.cs プロジェクト: MatrikMoon/EventSystem
        //GET the teams from the server
        private static IEnumerator GetTeams(SongListViewController slvc, Action <List <Team> > teamsGottenCallback = null)
        {
            UnityWebRequest www = UnityWebRequest.Get($"{discordCommunityApi}/teams/");

            Logger.Debug($"REQUESTING TEAMS: {discordCommunityApi}/teams/");
            www.timeout = 30;
            yield return(www.SendWebRequest());

            if (www.isNetworkError || www.isHttpError)
            {
                Logger.Error($"Error getting teams: {www.error}");
                slvc.ErrorHappened($"Error getting teams: {www.error}");
            }
            else
            {
                try
                {
                    //Clear out existing teams
                    Team.allTeams.Clear();

                    //Get the list of songs to download, and map out the song ids to the corresponding gamemodes
                    var node = JSON.Parse(www.downloadHandler.text);
                    foreach (var team in node)
                    {
                        var teamObject =
                            new Team(
                                team.Key,
                                team.Value["teamName"],
                                team.Value["captainId"],
                                team.Value["color"],
                                team.Value["requiredTokens"],
                                team.Value["nextPromotion"]
                                );
                        Team.allTeams.Add(teamObject);
                    }

                    teamsGottenCallback?.Invoke(Team.allTeams);
                }
                catch (Exception e)
                {
                    Logger.Error($"Error parsing teams data: {e}");
                    slvc.ErrorHappened($"Error parsing teams data: {e}");
                    yield break;
                }
            }
        }
コード例 #5
0
ファイル: Client.cs プロジェクト: MatrikMoon/EventSystem
        //GET the user's profile data from the server
        private static IEnumerator GetUserData(SongListViewController slvc, string userId, Action <Player> userDataGottenCallback = null)
        {
            UnityWebRequest www = UnityWebRequest.Get($"{discordCommunityApi}/playerstats/{userId}");

            Logger.Debug($"GETTING PLAYER DATA: {discordCommunityApi}/playerstats/{userId}");
            www.timeout = 30;
            yield return(www.SendWebRequest());

            if (www.isNetworkError || www.isHttpError)
            {
                Logger.Error($"Error getting player stats: {www.error}");
                slvc.ErrorHappened($"Error getting player stats: {www.error}");
            }
            else
            {
                try
                {
                    var node = JSON.Parse(www.downloadHandler.text);

                    //If there is a message from the server, display it
                    if (node["message"] != null && node["message"].ToString().Length > 1)
                    {
                        slvc.ErrorHappened(node["message"]);
                        yield break;
                    }

                    //If the client is out of date, show update message
                    if (VersionCode < Convert.ToInt32(node["version"].Value))
                    {
                        slvc.ErrorHappened($"Version {SharedConstructs.Version} is now out of date. Please download the newest one from the Discord.");
                    }

                    Player.Instance.Team          = node["team"];
                    Player.Instance.Tokens        = Convert.ToInt32(node["tokens"].Value);
                    Player.Instance.ServerOptions = (ServerFlags)Convert.ToInt32(node["serverSettings"].Value);

                    userDataGottenCallback?.Invoke(Player.Instance);
                }
                catch (Exception e)
                {
                    Logger.Error($"Error parsing playerstats data: {e}");
                    slvc.ErrorHappened($"Error parsing playerstats data: {e}");
                    yield break;
                }
            }
        }
コード例 #6
0
ファイル: SongUtils.cs プロジェクト: MatrikMoon/EventSystem
        public static async void PlaySong(IPreviewBeatmapLevel level, BeatmapCharacteristicSO characteristic, BeatmapDifficulty difficulty, OverrideEnvironmentSettings overrideEnvironmentSettings = null, ColorScheme colorScheme = null, GameplayModifiers gameplayModifiers = null, PlayerSpecificSettings playerSettings = null, Action <StandardLevelScenesTransitionSetupDataSO, LevelCompletionResults> songFinishedCallback = null)
        {
            Action <IBeatmapLevel> SongLoaded = (loadedLevel) =>
            {
                MenuTransitionsHelper _menuSceneSetupData = Resources.FindObjectsOfTypeAll <MenuTransitionsHelper>().First();
                _menuSceneSetupData.StartStandardLevel(
                    loadedLevel.beatmapLevelData.GetDifficultyBeatmap(characteristic, difficulty),
                    overrideEnvironmentSettings,
                    colorScheme,
                    gameplayModifiers ?? new GameplayModifiers(),
                    playerSettings ?? new PlayerSpecificSettings(),
                    null,
                    "Menu",
                    false,
                    null,
                    (standardLevelScenesTransitionSetupData, results) => songFinishedCallback?.Invoke(standardLevelScenesTransitionSetupData, results)
                    );
            };

            if ((level is PreviewBeatmapLevelSO && await HasDLCLevel(level.levelID)) ||
                level is CustomPreviewBeatmapLevel)
            {
                Logger.Debug("Loading DLC/Custom level...");
                var result = await GetLevelFromPreview(level);

                if (result != null && !(result?.isError == true))
                {
                    //HTTPstatus requires cover texture to be applied in here, and due to a fluke
                    //of beat saber, it's not applied when the level is loaded, but it *is*
                    //applied to the previewlevel it's loaded from
                    var loadedLevel = result?.beatmapLevel;
                    loadedLevel.SetField("_coverImageTexture2D", level.GetField <Texture2D>("_coverImageTexture2D"));
                    SongLoaded(loadedLevel);
                }
            }
            else if (level is BeatmapLevelSO)
            {
                Logger.Debug("Reading OST data without songloader...");
                SongLoaded(level as IBeatmapLevel);
            }
            else
            {
                Logger.Debug($"Skipping unowned DLC ({level.songName})");
            }
        }
コード例 #7
0
ファイル: Client.cs プロジェクト: MatrikMoon/EventSystem
        private static IEnumerator GetSongLeaderboardCoroutine(CustomLeaderboardController clc, string songHash, LevelDifficulty difficulty, string characteristic, string teamId = "-1", bool useTeamColors = false)
        {
            UnityWebRequest www = UnityWebRequest.Get($"{discordCommunityApi}/leaderboards/{songHash}/{(int)difficulty}/{characteristic}/{teamId}");

            www.timeout = 30;
            yield return(www.SendWebRequest());

            if (www.isNetworkError || www.isHttpError)
            {
                Logger.Error($"Error getting leaderboard data: {www.error}");
            }
            else
            {
                try
                {
                    var node = JSON.Parse(www.downloadHandler.text);

                    List <CustomLeaderboardTableView.CustomScoreData> scores = new List <CustomLeaderboardTableView.CustomScoreData>();
                    int myPos = -1;
                    foreach (var score in node)
                    {
                        scores.Add(new CustomLeaderboardTableView.CustomScoreData(
                                       Convert.ToInt32(score.Value["score"].ToString()),
                                       score.Value["player"],
                                       Convert.ToInt32(score.Value["place"].ToString()),
                                       score.Value["fullCombo"] == "true",
                                       score.Value["team"]
                                       ));

                        //If one of the scores is us, set the "special" score position to the right value
                        if (score.Value["userId"] == Convert.ToString(Plugin.UserId))
                        {
                            myPos = Convert.ToInt32(score.Value["place"] - 1);
                        }
                    }
                    clc.SetScores(scores, myPos, useTeamColors);
                }
                catch (Exception e)
                {
                    Logger.Error($"Error parsing leaderboard data: {e}");
                    yield break;
                }
            }
        }
コード例 #8
0
ファイル: SongUtils.cs プロジェクト: MatrikMoon/EventSystem
        //Returns the closest difficulty to the one provided, preferring lower difficulties first if any exist
        public static IDifficultyBeatmap GetClosestDifficultyPreferLower(IBeatmapLevel level, BeatmapDifficulty difficulty, string characteristic)
        {
            //First, look at the characteristic parameter. If there's something useful in there, we try to use it, but fall back to Standard
            var desiredCharacteristic = level.previewDifficultyBeatmapSets.FirstOrDefault(x => x.beatmapCharacteristic.serializedName == characteristic).beatmapCharacteristic ?? level.previewDifficultyBeatmapSets.First().beatmapCharacteristic;

            IDifficultyBeatmap[] availableMaps =
                level
                .beatmapLevelData
                .difficultyBeatmapSets
                .FirstOrDefault(x => x.beatmapCharacteristic.serializedName == desiredCharacteristic.serializedName)
                .difficultyBeatmaps
                .OrderBy(x => x.difficulty)
                .ToArray();

            IDifficultyBeatmap ret = availableMaps.FirstOrDefault(x => x.difficulty == difficulty);

            if (ret is CustomDifficultyBeatmap)
            {
                var extras       = Collections.RetrieveExtraSongData(ret.level.levelID);
                var requirements = extras?._difficulties.First(x => x._difficulty == ret.difficulty).additionalDifficultyData._requirements;
                Logger.Debug($"{ret.level.songName} is a custom level, checking for requirements on {ret.difficulty}...");
                if (
                    (requirements?.Count() > 0) &&
                    (!requirements?.ToList().All(x => Collections.capabilities.Contains(x)) ?? false)
                    )
                {
                    ret = null;
                }
                Logger.Debug((ret == null ? "Requirement not met." : "Requirement met!"));
            }

            if (ret == null)
            {
                ret = GetLowerDifficulty(availableMaps, difficulty, desiredCharacteristic);
            }
            if (ret == null)
            {
                ret = GetHigherDifficulty(availableMaps, difficulty, desiredCharacteristic);
            }

            return(ret);
        }
コード例 #9
0
ファイル: SongUtils.cs プロジェクト: MatrikMoon/EventSystem
        //Returns the next-lowest difficulty to the one provided
        private static IDifficultyBeatmap GetLowerDifficulty(IDifficultyBeatmap[] availableMaps, BeatmapDifficulty difficulty, BeatmapCharacteristicSO characteristic)
        {
            var ret = availableMaps.TakeWhile(x => x.difficulty < difficulty).LastOrDefault();

            if (ret is CustomDifficultyBeatmap)
            {
                var extras       = Collections.RetrieveExtraSongData(ret.level.levelID);
                var requirements = extras?._difficulties.First(x => x._difficulty == ret.difficulty).additionalDifficultyData._requirements;
                Logger.Debug($"{ret.level.songName} is a custom level, checking for requirements on {ret.difficulty}...");
                if (
                    (requirements?.Count() > 0) &&
                    (!requirements?.ToList().All(x => Collections.capabilities.Contains(x)) ?? false)
                    )
                {
                    ret = null;
                }
                Logger.Debug((ret == null ? "Requirement not met." : "Requirement met!"));
            }
            return(ret);
        }
コード例 #10
0
ファイル: Client.cs プロジェクト: MatrikMoon/EventSystem
        //Post a score to the server
        private static IEnumerator PostCoroutine(string data, string address, Action <bool> postCompleteCallback = null)
        {
            //TODO: REMOVE. Temp logging in for some players randomly failing to submit score
            Logger.Info($"Posting: {address} {data}");

            UnityWebRequest www = UnityWebRequest.Post(address, data);

            www.timeout = 30;
            yield return(www.SendWebRequest());

            if (www.isNetworkError || www.isHttpError)
            {
                Logger.Error($"{www.responseCode} : {www.error}");
                postCompleteCallback?.Invoke(false);
            }
            else
            {
                postCompleteCallback?.Invoke(true);
            }
        }
コード例 #11
0
        private void SongFinished(StandardLevelScenesTransitionSetupDataSO standardLevelScenesTransitionSetupData, LevelCompletionResults results)
        {
            standardLevelScenesTransitionSetupData.didFinishEvent -= SongFinished;

            var map          = _communityLeaderboard.selectedSong.Beatmap;
            var localPlayer  = _playerDataModel.playerData;
            var localResults = localPlayer.GetPlayerLevelStatsData(map.level.levelID, map.difficulty, map.parentDifficultyBeatmapSet.beatmapCharacteristic);
            var highScore    = localResults.highScore < results.modifiedScore;

            var scoreLights   = _soloFreePlayFlowCoordinator.GetField <MenuLightsPresetSO>("_resultsLightsPreset");
            var redLights     = _campaignFlowCoordinator.GetField <MenuLightsPresetSO>("_newObjectiveLightsPreset");
            var defaultLights = _soloFreePlayFlowCoordinator.GetField <MenuLightsPresetSO>("_defaultLightsPreset");

            if (results.levelEndAction == LevelCompletionResults.LevelEndAction.Restart)
            {
                PlaySong(_communityLeaderboard.selectedSong);
            }
            else
            {
                if (results.levelEndStateType == LevelCompletionResults.LevelEndStateType.Cleared) //Didn't quit and didn't die
                {
                    //If bs_utils disables score submission, we do too
                    if (BSUtilsScoreDisabled())
                    {
                        return;
                    }

                    IBeatmapLevel level    = _communityLeaderboard.selectedSong.Beatmap.level;
                    string        songHash = SongUtils.GetHashFromLevelId(level.levelID);

                    //Community leaderboards
                    var    song   = _communityLeaderboard.selectedSong;
                    string signed = RSA.SignScore(Plugin.UserId, song.Hash, (int)_communityLeaderboard.selectedSong.Beatmap.difficulty, _communityLeaderboard.selectedSong.Characteristic, results.fullCombo, results.rawScore, (int)song.PlayerOptions, (int)song.GameOptions);

                    Action <bool> scoreUpdateComplete = (b) =>
                    {
                        Logger.Success($"Score upload {(b ? "compete" : "failed")}!");
                        if (b && _communityLeaderboard)
                        {
                            UnityMainThreadDispatcher.Instance().Enqueue(() =>
                            {
                                _communityLeaderboard.Refresh();
                            });
                        }
                    };
                    Client.SubmitScore(Plugin.UserId, song.Hash, (int)_communityLeaderboard.selectedSong.Beatmap.difficulty, _communityLeaderboard.selectedSong.Characteristic, results.fullCombo, results.rawScore, signed, (int)song.PlayerOptions, (int)song.GameOptions, scoreUpdateComplete);

                    Action <ResultsViewController> resultsContinuePressed = null;
                    resultsContinuePressed = (e) =>
                    {
                        _resultsViewController.continueButtonPressedEvent -= resultsContinuePressed;
                        _menuLightsManager.SetColorPreset(defaultLights, true);
                        DismissViewController(_resultsViewController);
                    };

                    _menuLightsManager.SetColorPreset(scoreLights, true);
                    _resultsViewController.Init(results, map, false, highScore);
                    _resultsViewController.continueButtonPressedEvent += resultsContinuePressed;
                    PresentViewController(_resultsViewController, null, true);
                }
            }
        }
コード例 #12
0
        //BSUtils: disable gameplay-modifying plugins

        private void BSUtilsDisableOtherPlugins()
        {
            BS_Utils.Gameplay.Gamemode.NextLevelIsIsolated("EventPlugin");
            Logger.Debug("Disabled game-modifying plugins through bs_utils :)");
        }
コード例 #13
0
ファイル: Client.cs プロジェクト: MatrikMoon/EventSystem
        //Download songs. Taken from BeatSaberMultiplayer
        //availableSongs: List of IBeatmapLevel which may hold levels already approved for display
        //downloadQueue: List of beatsaver ids representing songs left to download
        //completedDownloads: List of beatsaver ids representing songs that have successfully downloaded
        //songId: The song this instance of the Coroutine is supposed to download
        //slvc: The song list view controller to display the downloaded songs to
        private static IEnumerator DownloadSongs(string songHash, SongListViewController slvc)
        {
            UnityWebRequest www = UnityWebRequest.Get($"{beatSaverDownloadUrl}{songHash}");

#if BETA
            Logger.Info($"DOWNLOADING: {beatSaverDownloadUrl}{songHash}");
#endif
            bool  timeout = false;
            float time    = 0f;

            www.SetRequestHeader("user-agent", @"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36");
            UnityWebRequestAsyncOperation asyncRequest = www.SendWebRequest();

            while (!asyncRequest.isDone || asyncRequest.progress < 1f)
            {
                yield return(null);

                time += Time.deltaTime;

                if (time >= 15f && asyncRequest.progress == 0f)
                {
                    www.Abort();
                    timeout = true;
                }
            }

            if (www.isNetworkError || www.isHttpError || timeout)
            {
                Logger.Error($"Error downloading song {songHash}: {www.error}");
                slvc.ErrorHappened($"Error downloading song {songHash}: {www.error}");
            }
            else
            {
                //Logger.Info("Received response from BeatSaver.com...");

                string zipPath         = "";
                string customSongsPath = CustomLevelPathHelper.customLevelsDirectoryPath;
                string customSongPath  = "";

                byte[] data = www.downloadHandler.data;

                try
                {
                    customSongPath = customSongsPath + "/" + songHash + "/";
                    zipPath        = customSongPath + songHash + ".zip";
                    if (!Directory.Exists(customSongPath))
                    {
                        Directory.CreateDirectory(customSongPath);
                    }
                    File.WriteAllBytes(zipPath, data);
                    //Logger.Info("Downloaded zip file!");
                }
                catch (Exception e)
                {
                    Logger.Error($"Error writing zip: {e}");
                    slvc.ErrorHappened($"Error writing zip: {e}");
                    yield break;
                }

                //Logger.Info("Extracting...");

                try
                {
                    ZipFile.ExtractToDirectory(zipPath, customSongPath);
                }
                catch (Exception e)
                {
                    Logger.Error($"Unable to extract ZIP! Exception: {e}");
                    slvc.ErrorHappened($"Unable to extract ZIP! Exception: {e}");
                    yield break;
                }

                try
                {
                    File.Delete(zipPath);
                }
                catch (IOException e)
                {
                    Logger.Warning($"Unable to delete zip! Exception: {e}");
                    slvc.ErrorHappened($"Unable to delete zip! Exception: {e}");
                    yield break;
                }

                Logger.Success($"Downloaded!");
            }
        }
コード例 #14
0
ファイル: Client.cs プロジェクト: MatrikMoon/EventSystem
        //GET the songs from the server, then start the Download coroutine to download and display them
        //TODO: Time complexity here is a mess.
        private static IEnumerator GetSongs(SongListViewController slvc, string userId, Action <List <Song> > songsGottenCallback = null)
        {
            UnityWebRequest www = UnityWebRequest.Get($"{discordCommunityApi}/songs/{userId}/");

            Logger.Debug($"REQUESTING SONGS: {discordCommunityApi}/songs/{userId}/");

            www.timeout = 30;
            yield return(www.SendWebRequest());

            if (www.isNetworkError || www.isHttpError)
            {
                Logger.Error($"Error getting songs: {www.error}");
                slvc.ErrorHappened($"Error getting songs: {www.error}");
            }
            else
            {
                List <Song> songs = new List <Song>();
                try
                {
                    //Get the list of songs to download, and map out the song ids to the corresponding gamemodes
                    var node = JSON.Parse(www.downloadHandler.text);
                    foreach (var id in node)
                    {
                        var newSong = new Song()
                        {
                            Hash           = id.Value["songHash"],
                            SongName       = id.Value["songName"],
                            GameOptions    = (GameOptions)Convert.ToInt32(id.Value["gameOptions"].ToString()),
                            PlayerOptions  = (PlayerOptions)Convert.ToInt32(id.Value["playerOptions"].ToString()),
                            Difficulty     = (LevelDifficulty)Convert.ToInt32(id.Value["difficulty"].ToString()),
                            Characteristic = id.Value["characteristic"]
                        };

                        Logger.Debug($"ADDING SONG: {newSong.SongName} {newSong.Difficulty} {newSong.Characteristic}");
                        songs.Add(newSong);
                    }
                }
                catch (Exception e)
                {
                    Logger.Error($"Error parsing getsong data: {e}");
                    slvc.ErrorHappened($"Error parsing getsong data: {e}");
                    yield break;
                }

                //If we got songs, filter them as neccessary then download any we don't have
                List <Song> availableSongs = new List <Song>();

                //Filter out songs we already have and OSTS
                IEnumerable <Song> osts        = songs.Where(x => OstHelper.IsOst(x.Hash));
                IEnumerable <Song> alreadyHave = songs.Where(x => Collections.songWithHashPresent(x.Hash.ToUpper()));

                //Loads a level from a song instance, populates the Beatmap property and adds to the available list
                Action <Song> loadLevel = (song) =>
                {
                    if (Collections.songWithHashPresent(song.Hash.ToUpper()))
                    {
                        var levelId = Collections.levelIDsForHash(song.Hash).First();

                        var customPreview = Loader.CustomLevelsCollection.beatmapLevels.First(x => x.levelID == levelId) as CustomPreviewBeatmapLevel;

                        song.PreviewBeatmap = customPreview;

                        //TODO: Figure out proper async-ness here

                        /*var beatmapLevelResult = Task.Run(async () => await SongUtils.GetLevelFromPreview(customPreview));
                         * beatmapLevelResult.Wait();
                         *
                         * //TODO: add characteristic name field to the song data stored in the server
                         * song.Beatmap = SongUtils.GetClosestDifficultyPreferLower(beatmapLevelResult.Result?.beatmapLevel, (BeatmapDifficulty)song.Difficulty);
                         * availableSongs.Add(song);*/
                    }
                    else
                    {
                        slvc.ErrorHappened($"Could not load level {song.SongName}");
                    }
                };

                //Load the preview levels for what we have
                foreach (Song song in osts)
                {
                    foreach (IBeatmapLevelPack pack in Loader.BeatmapLevelsModelSO.allLoadedBeatmapLevelPackCollection.beatmapLevelPacks)
                    {
                        var foundLevel = pack.beatmapLevelCollection.beatmapLevels.FirstOrDefault(y => y.levelID.ToLower() == song.Hash.ToLower());
                        if (foundLevel != null)
                        {
                            song.PreviewBeatmap = foundLevel;
                        }
                    }
                }

                foreach (Song song in alreadyHave)
                {
                    loadLevel(song);
                }

                //Of what we already have, add the Levels to the availableSongs list
                availableSongs.AddRange(alreadyHave);
                availableSongs.AddRange(osts);

                //Remove what we already have from the download queue
                songs.RemoveAll(x => availableSongs.Contains(x)); //Don't redownload

                //Download the things we don't have, or if we have everything, show the menu
                if (songs.Count > 0)
                {
                    List <IEnumerator> downloadCoroutines = new List <IEnumerator>();
                    songs.ForEach(x =>
                    {
                        downloadCoroutines.Add(DownloadSongs(x.Hash, slvc));
                    });

                    //Wait for the all downloads to finish
                    yield return(SharedCoroutineStarter.instance.StartCoroutine(new ParallelCoroutine().ExecuteCoroutines(downloadCoroutines.ToArray())));

                    Action <Loader, Dictionary <string, CustomPreviewBeatmapLevel> > songsLoaded =
                        (_, __) =>
                    {
                        //Now that they're refreshed, we can populate their beatmaps and add them to the available list
                        songs.ForEach(x => loadLevel(x));
                        songsGottenCallback?.Invoke(availableSongs.Union(songs).ToList());
                    };

                    Loader.SongsLoadedEvent -= songsLoaded;
                    Loader.SongsLoadedEvent += songsLoaded;
                    Loader.Instance.RefreshSongs(false);
                }
                else
                {
                    songsGottenCallback?.Invoke(availableSongs);
                }
            }
        }