示例#1
0
        /// <summary>
        /// Update all files within a directory to correct variable names
        /// </summary>
        /// <param name="folderPath">In folder path</param>
        /// <param name="keyVars">dictionary of words to replace within files</param>
        /// <param name="checkSubDirectories">Recursive update files and folders</param>
        /// <param name="excludeExtensions">Blacklist of file extensions not to update</param>
        public static void UpdateAllCopiedFiles(string folderPath, Dictionary <string, string> keyVars, bool checkSubDirectories = false, string[] excludeExtensions = null)
        {
            if (checkSubDirectories)
            {
                string[] dirs = SafeFileManagement.GetDirectoryPaths(folderPath, Globals.NUMBER_OF_IO_RETRY_ATTEMPTS);
                foreach (string dir in dirs)
                {
                    UpdateAllCopiedFiles(dir, keyVars, checkSubDirectories, excludeExtensions);
                }
            }

            if (Directory.Exists(folderPath))
            {
                string[] files = SafeFileManagement.GetFilesPaths(folderPath, Globals.NUMBER_OF_IO_RETRY_ATTEMPTS);
                foreach (string file in files)
                {
                    if (excludeExtensions != null)
                    {
                        string extension = Path.GetExtension(file);
                        if (Array.Exists(excludeExtensions, element => element.ToLower() == extension.ToLower()))
                        {
                            continue;
                        }
                    }
                    UpdateFileWithKeys(file, keyVars);
                }
            }
        }
示例#2
0
        /// <summary>
        /// Convert Beat Saber data to a minecraft ResourcePack
        /// </summary>
        /// <param name="unzippedFolderPath">Path to unzipped beat saber pack</param>
        /// <param name="datapackOutputPath">Folder path that Resourcepack will be generated in</param>
        /// <param name="packInfo">Beat Saber Infomation</param>
        /// <returns>-1 if successful</returns>
        public static async Task <ConversionError> FromBeatSaberData(string datapackOutputPath, BeatSaberMap beatSaberMap)
        {
            return(await Task.Run(() =>
            {
                var packInfo = beatSaberMap.InfoData;
                var unzippedFolderPath = beatSaberMap.ExtractedFilePath;
                if (!Directory.Exists(unzippedFolderPath) || packInfo == null)
                {
                    return ConversionError.MissingInfo;
                }

                Dictionary <string, string> keyVars = new Dictionary <string, string>();

                string folder_uuid = SafeFileManagement.GetFileName(Path.GetFileName(unzippedFolderPath)).MakeMinecraftSafe();
                string packName = Globals.RESOURCEPACK + folder_uuid;

                // Paths
                string fullOutputPath = Path.Combine(datapackOutputPath, packName + Globals.ZIP);
                string rootFolderPath = Path.Combine(unzippedFolderPath, packName);
                string minecraftNamespace = Path.Combine(rootFolderPath, Globals.ASSETS, Globals.MINECRAFT);
                string mapSong = Path.Combine(unzippedFolderPath, packInfo.SongFilename);
                string packSong = Path.Combine(minecraftNamespace, Globals.SOUNDS, Globals.CUSTOM, folder_uuid + Globals.OGG);
                string mapIcon = Path.Combine(unzippedFolderPath, packInfo.CoverImageFilename);
                string packIcon = Path.Combine(rootFolderPath, Globals.PACK_ICON);

                // Replaced vars
                keyVars["SONGUUID"] = folder_uuid;
                keyVars["SONGNAME"] = packInfo.SongName + packInfo.SongSubName;
                keyVars["AUTHORNAME"] = packInfo.SongAuthorName;

                // Copying Template
                string copiedTemplatePath = Path.Combine(unzippedFolderPath, Globals.TEMPLATE_RESOURCES_PACK_NAME);

                if (SafeFileManagement.DirectoryCopy(Globals.pathOfResourcepackTemplate, unzippedFolderPath, true, Globals.excludeExtensions, Globals.NUMBER_OF_IO_RETRY_ATTEMPTS))
                {
                    if (SafeFileManagement.MoveDirectory(copiedTemplatePath, rootFolderPath, Globals.NUMBER_OF_IO_RETRY_ATTEMPTS))
                    {
                        Filemanagement.UpdateAllCopiedFiles(rootFolderPath, keyVars);

                        // Copying Image Icon
                        SafeFileManagement.CopyFileTo(mapIcon, packIcon, true, Globals.NUMBER_OF_IO_RETRY_ATTEMPTS);

                        // Copying Song
                        if (SafeFileManagement.CopyFileTo(mapSong, packSong, true, Globals.NUMBER_OF_IO_RETRY_ATTEMPTS))
                        {
                            if (!Filemanagement.UpdateFileWithKeys(Path.Combine(minecraftNamespace, Globals.SOUNDS_JSON), keyVars))
                            {
                                return ConversionError.OtherFail;
                            }
                        }

                        // Creating Zip
                        Archive.Compress(rootFolderPath, fullOutputPath, true);
                        return ConversionError.None;
                    }
                }
                return ConversionError.FailedToCopyFile;
            }));
        }
        /// <summary>
        /// Convert a Beat Saber Zip file into a Minecraft Resourcepack and Datapack
        /// </summary>
        /// <param name="zipPath">file path to beat saber zip file</param>
        /// <param name="datapackOutputPath">folder path that minecraft zips will be generated</param>
        /// <param name="uuid">Unique number that determines song value</param>
        /// <param name="cancellationToken">Token that allows async function to be canceled</param>
        /// <returns>-1 on Success</returns>
        public static async Task <ConversionError> ConvertAsync(string zipPath, string datapackOutputPath, int uuid, IProgress <ConversionProgress> progress, CancellationToken cancellationToken)
        {
            if (!File.Exists(zipPath) || !Directory.Exists(datapackOutputPath))
            {
                return(ConversionError.MissingInfo);
            }
            progress.Report(new ConversionProgress(0.1f, "Loading beat map file"));
            var beatSaberMap = await MapLoader.GetDataFromMapZip(zipPath, ProcessManager.temporaryPath, cancellationToken);

            if (beatSaberMap == null)
            {
                return(ConversionError.InvalidBeatMap);
            }
            cancellationToken.ThrowIfCancellationRequested();
            var tempFolder = beatSaberMap.ExtractedFilePath;

            try
            {
                beatSaberMap = ConvertFilesEggToOgg(beatSaberMap);
                beatSaberMap = ConvertFilesJpgToPng(beatSaberMap);
                if (beatSaberMap.InfoData.DifficultyBeatmapSets.Length == 0)
                {
                    return(ConversionError.NoMapData);
                }
                cancellationToken.ThrowIfCancellationRequested();
                // Generating Resource pack
                progress.Report(new ConversionProgress(0.2f, "Generating resource pack"));
                var resourcepackError = await ResourcePack.FromBeatSaberData(datapackOutputPath, beatSaberMap);

                if (resourcepackError != ConversionError.None)
                {
                    return(resourcepackError);
                }
                cancellationToken.ThrowIfCancellationRequested();
                // Generating Data pack
                progress.Report(new ConversionProgress(0.3f, "Generating datapack"));
                var datapackError = await DataPack.FromBeatSaberData(datapackOutputPath, beatSaberMap, progress, cancellationToken);

                if (datapackError != ConversionError.None)
                {
                    return(datapackError);
                }
            }
            catch (OperationCanceledException e)
            {
                SafeFileManagement.DeleteDirectory(tempFolder);
                throw (e);
            }
            catch (ObjectDisposedException)
            {
                SafeFileManagement.DeleteDirectory(tempFolder);
            }

            // Successfully converted map
            SafeFileManagement.DeleteDirectory(tempFolder);
            return(ConversionError.None);
        }
 public static BeatSaberMap ConvertFilesJpgToPng(BeatSaberMap beatSaberMap)
 {
     string[] files = Directory.GetFiles(beatSaberMap.ExtractedFilePath, "*.jpg*", SearchOption.AllDirectories);
     beatSaberMap.InfoData.CoverImageFilename = beatSaberMap.InfoData.CoverImageFilename.Replace(".jpg", ".png");
     foreach (string path in files)
     {
         string newName = path.Replace(".jpg", ".png");
         SafeFileManagement.MoveFile(path, newName);
     }
     return(beatSaberMap);
 }
示例#5
0
        /// <summary>
        /// Replace any keys within a file from a dictionary
        /// </summary>
        /// <param name="filePath">path of file to replace keys in</param>
        /// <param name="keyVars">dictionary of words to replace within files</param>
        public static bool UpdateFileWithKeys(string filePath, Dictionary <string, string> keyVars)
        {
            string textInfo = SafeFileManagement.GetFileContents(filePath);

            if (textInfo == null)
            {
                return(false);
            }
            foreach (string key in keyVars.Keys)
            {
                textInfo = textInfo.Replace(key, keyVars[key]);
            }
            return(SafeFileManagement.SetFileContents(filePath, textInfo));
        }
        public DataPackData(string unzippedFolderPath, string datapackOutputPath, BeatSaberMap beatSaberMap)
        {
            var packInfo = beatSaberMap.InfoData;

            keyVars     = new Dictionary <string, string>();
            folder_uuid = SafeFileManagement.GetFileName(Path.GetFileName(unzippedFolderPath)).MakeMinecraftSafe();
            packName    = Globals.DATAPACK + folder_uuid;
            songGuid    = beatSaberMap.GuidId.ToString();

            // Paths
            datapackRootPath            = Path.Combine(unzippedFolderPath, packName);
            fullOutputPath              = Path.Combine(datapackOutputPath, packName + Globals.ZIP);
            blockSaberBaseFunctionsPath = Path.Combine(datapackRootPath, Globals.DATA, Globals.BLOCK_SABER_BASE, Globals.FUNCTIONS);
            folder_uuidFunctionsPath    = Path.Combine(datapackRootPath, Globals.DATA, folder_uuid, Globals.FUNCTIONS);
            spawnNotesBasePath          = Path.Combine(folder_uuidFunctionsPath, Globals.SPAWN_NOTES_BASE_FUNCTION);

            // Values
            metersPerTick    = packInfo.BeatsPerMinute / 60.0d * 24 * 0.21 / 20;
            ticksStartOffset = (int)(Mathf.Clamp((float)(packInfo.BeatsPerMinute / 60d * 10), 7, 20) / metersPerTick);

            // Set up Keys
            keyVars["MAPPER_NAME"]      = packInfo.LevelAuthorName;
            keyVars["BEATS_PER_MINUTE"] = packInfo.BeatsPerMinute.ToString();
            keyVars["SONGID"]           = beatSaberMap.GuidId.GetHashCode().ToString();
            keyVars["MOVESPEED"]        = metersPerTick.ToString();
            keyVars["SONGTITLE"]        = packInfo.SongName + " " + packInfo.SongSubName;
            keyVars["SONGSHORTNAME"]    = packInfo.SongName;
            keyVars["SONGARTIST"]       = packInfo.SongAuthorName;
            keyVars["folder_uuid"]      = folder_uuid;
            keyVars["SONGDIFFICULTYID"] = songGuid + "1";

            StringBuilder listOfDifficulties = new StringBuilder();
            var           beatMapSets        = beatSaberMap.InfoData.DifficultyBeatmapSets;

            for (int beatMapCounts = 0; beatMapCounts < beatMapSets.Length; beatMapCounts++)
            {
                var beatMapInfos = beatMapSets[beatMapCounts].DifficultyBeatmaps;
                int beatMapCount = beatMapInfos.Length;
                for (int difficulty = 0; difficulty < beatMapCount; difficulty++)
                {
                    listOfDifficulties.Append(beatMapInfos[difficulty].Difficulty);
                    if (difficulty < beatMapCount - 1)
                    {
                        listOfDifficulties.Append(" | ");
                    }
                }
            }
            keyVars["DIFFICULTYLIST"] = listOfDifficulties.ToString();
        }
 /// <summary>
 /// Convert images file to png
 /// </summary>
 /// <param name="rootFilePath">Folder that contains .jpg files</param>
 /// <param name="packInfo">info object that contains data about beatsaber songs</param>
 /// <returns>updated pack info object</returns>
 public static Info ConvertImageFiles(string rootFilePath, Info packInfo)
 {
     string[] files = Directory.GetFiles(rootFilePath, "*.jpg*", SearchOption.AllDirectories);
     if (string.IsNullOrEmpty(packInfo.CoverImageFilename))
     {
         return(null);
     }
     packInfo.CoverImageFilename = packInfo.CoverImageFilename.Replace(".jpg", ".png");
     foreach (string path in files)
     {
         string newName = path.Replace(".jpg", ".png");
         SafeFileManagement.MoveFile(path, newName);
     }
     return(packInfo);
 }
 public static BeatSaberMap ConvertFilesEggToOgg(BeatSaberMap beatSaberMap)
 {
     string[] files = Directory.GetFiles(beatSaberMap.ExtractedFilePath, "*.egg*", SearchOption.AllDirectories);
     string[] alreadyConvertedfiles = Directory.GetFiles(beatSaberMap.ExtractedFilePath, "*.ogg*", SearchOption.AllDirectories);
     if (files.Length == 0 && alreadyConvertedfiles.Length == 0)
     {
         return(beatSaberMap);
     }
     beatSaberMap.InfoData.SongFilename = beatSaberMap.InfoData.SongFilename.Replace(".egg", ".ogg");
     foreach (string path in files)
     {
         string newName = path.Replace(".egg", ".ogg");
         SafeFileManagement.MoveFile(path, newName);
     }
     return(beatSaberMap);
 }
        /// <summary>
        /// Get all data from beatsaber map zip file
        /// </summary>
        /// <param name="fileToUnzip">path to beat saber map zip</param>
        /// <param name="pathToUnzip">path to unzip map data</param>
        /// <param name="cancellationToken">token to cancel action</param>
        /// <returns></returns>
        public static async Task <BeatSaberMap> GetDataFromMapZip(string fileToUnzip, string pathToUnzip, CancellationToken cancellationToken)
        {
            string tempUnZipPath = Path.Combine(pathToUnzip, "MapLoader", SafeFileManagement.GetFolderName(fileToUnzip));

            if (Directory.Exists(tempUnZipPath))
            {
                SafeFileManagement.DeleteDirectory(tempUnZipPath);
            }
            Directory.CreateDirectory(tempUnZipPath);
            await Archive.DecompressAsync(fileToUnzip, tempUnZipPath, cancellationToken);

            cancellationToken.ThrowIfCancellationRequested();
            return(await Task.Run(() =>
            {
                string infoPath = Path.Combine(tempUnZipPath, "info.dat");
                Info info = JsonUtility.FromJson <Info>(SafeFileManagement.GetFileContents(infoPath));
                if (info == null)
                {
                    return null;
                }
                info = ConvertSoundFile(tempUnZipPath, info);
                if (info == null)
                {
                    return null;
                }
                info = ConvertImageFiles(tempUnZipPath, info);
                if (info == null)
                {
                    return null;
                }

                cancellationToken.ThrowIfCancellationRequested();

                Dictionary <string, MapDataInfo> mapDataInfos = new Dictionary <string, MapDataInfo>();
                foreach (var beatMapSets in info.DifficultyBeatmapSets)
                {
                    foreach (var beatMap in beatMapSets.DifficultyBeatmaps)
                    {
                        string mapPath = Path.Combine(tempUnZipPath, beatMap.BeatmapFilename);
                        MapData mapData = JsonUtility.FromJson <MapData>(SafeFileManagement.GetFileContents(mapPath));
                        mapDataInfos.Add(beatMap.BeatmapFilename, new MapDataInfo(beatMap, mapData));
                    }
                }
                return new BeatSaberMap(info, mapDataInfos, tempUnZipPath);
            }));
        }
 /// <summary>
 /// Convert egg file to ogg
 /// </summary>
 /// <param name="rootFilePath">Folder that contains .egg files</param>
 /// <param name="info">Info object that contains data about beatsaber songs</param>
 /// <returns>Updated pack info object</returns>
 public static Info ConvertSoundFile(string rootFilePath, Info info)
 {
     string[] files = Directory.GetFiles(rootFilePath, "*.egg*", SearchOption.AllDirectories);
     string[] alreadyConvertedfiles = Directory.GetFiles(rootFilePath, "*.ogg*", SearchOption.AllDirectories);
     if (string.IsNullOrEmpty(info.SongFilename))
     {
         return(null);
     }
     info.SongFilename = info.SongFilename.Replace(".egg", ".ogg");
     if (files.Length == 0 && alreadyConvertedfiles.Length == 0)
     {
         return(null);
     }
     foreach (string path in files)
     {
         string newName = path.Replace(".egg", ".ogg");
         SafeFileManagement.MoveFile(path, newName);
     }
     return(info);
 }
示例#11
0
 public void OpenOutputPath()
 {
     SafeFileManagement.OpenFolder(OutputPath);
 }
        /// <summary>
        /// Generate obstacles commands given a song and difficulty
        /// </summary>
        /// <param name="song">Beatmap data for a song and difficulty</param>
        /// <param name="difficultyName">Minecraft safe difficulty name</param>
        /// <param name="commandBasePath">Base folder path to generate new mcfunctions</param>
        /// <param name="packInfo">Beat Saber Parsed info</param>
        /// <param name="dpd">Data used for datapack generation</param>
        public static void GenerateObstacles(MapData song, string difficultyName, string commandBasePath, Info packInfo, DataPackData dpd)
        {
            Obstacle[] obstacles               = song.Obstacles;
            int        obstacleIndex           = 0;
            int        currentLevel            = 1;
            int        currentTick             = 0;
            int        maxTick                 = 0;
            int        minTick                 = -1;
            int        currentNumberOfCommands = 0;
            int        currentCommandLimit     = Globals.COMMAND_LIMIT;

            // Main note generation
            while (obstacleIndex < obstacles.Length)
            {
                string        commandLevelName     = difficultyName + Globals.LEVEL_OBSTACLE_NAME + currentLevel;
                string        commandLevelFileName = commandLevelName + Globals.MCFUNCTION;
                string        commandLevelFilePath = Path.Combine(dpd.folder_uuidFunctionsPath, commandLevelFileName);
                StringBuilder currentCommands      = new StringBuilder();
                int           maxNewTick           = 0;
                int           minNewTick           = 0;
                minTick = -1;

                // Continue to generate commands until all nodes and obstacles have been iterated though
                while (obstacleIndex < obstacles.Length && currentNumberOfCommands < currentCommandLimit)
                {
                    ObstacleDataToCommands(obstacles[obstacleIndex], packInfo.BeatsPerMinute, dpd.metersPerTick, ref currentCommands, ref currentNumberOfCommands, ref minNewTick, ref maxNewTick);
                    if (minTick == -1)
                    {
                        minTick = minNewTick;
                    }
                    maxTick = Mathf.Max(maxTick, maxNewTick);
                    obstacleIndex++;
                }

                if (obstacleIndex >= obstacles.Length)
                {
                    maxTick += (int)(dpd.ticksStartOffset + 1);
                    currentCommands.AppendFormat(Globals.templateStrings._finishedObstacles, maxTick);
                    if (minTick == -1)
                    {
                        minTick = maxTick - 1;
                    }
                }

                SafeFileManagement.SetFileContents(commandLevelFilePath, currentCommands.ToString());
                string baseCommand = string.Format(Globals.templateStrings._baseCommand, minTick, maxTick, dpd.folder_uuid, commandLevelName);
                SafeFileManagement.AppendFile(commandBasePath, baseCommand);
                currentCommandLimit = currentNumberOfCommands + Globals.COMMAND_LIMIT;
                minTick             = 0;
                maxTick             = 0;
                currentLevel++;
            }

            if (obstacles.Length == 0)
            {
                string        commandLevelName     = difficultyName + Globals.LEVEL_OBSTACLE_NAME + currentLevel;
                string        commandLevelFileName = commandLevelName + Globals.MCFUNCTION;
                string        commandLevelFilePath = Path.Combine(dpd.folder_uuidFunctionsPath, commandLevelFileName);
                StringBuilder currentCommands      = new StringBuilder();
                currentTick++;
                currentCommands.AppendFormat(Globals.templateStrings._finishedObstacles, currentTick);
                SafeFileManagement.SetFileContents(commandLevelFilePath, currentCommands.ToString());
                string baseCommand = string.Format(Globals.templateStrings._baseCommand,
                                                   minTick,
                                                   maxTick + (int)(dpd.ticksStartOffset + 1),
                                                   dpd.folder_uuid,
                                                   commandLevelName);
                SafeFileManagement.AppendFile(commandBasePath, baseCommand);
            }
        }
        /// <summary>
        /// Generate a minecraft datapack from Beat Saber data
        /// </summary>
        /// <param name="unzippedFolderPath">Path of unzipped Beat Saber data</param>
        /// <param name="datapackOutputPath">Path to output datapack</param>
        /// <param name="packInfo">Beat Saber Parsed info</param>
        /// <param name="beatMapSongList">List of Beat Saber song data</param>
        /// <param name="cancellationToken">Token that allows async function to be canceled</param>
        /// <returns></returns>
        public static async Task <ConversionError> FromBeatSaberData(string datapackOutputPath, BeatSaberMap beatSaberMap, IProgress <ConversionProgress> progress, CancellationToken cancellationToken)
        {
            return(await Task.Run(() =>
            {
                var unzippedFolderPath = beatSaberMap.ExtractedFilePath;
                if (!Directory.Exists(unzippedFolderPath))
                {
                    return Task.FromResult(ConversionError.UnzipError);
                }
                DataPackData dataPackData = new DataPackData(unzippedFolderPath, datapackOutputPath, beatSaberMap);
                if (beatSaberMap.InfoData.DifficultyBeatmapSets.Length == 0)
                {
                    return Task.FromResult(ConversionError.NoMapData);
                }
                // Copying Template
                string copiedTemplatePath = Path.Combine(unzippedFolderPath, Globals.TEMPLATE_DATA_PACK_NAME);
                if (!SafeFileManagement.DirectoryCopy(Globals.pathOfDatapackTemplate, unzippedFolderPath, true, Globals.excludeExtensions, Globals.NUMBER_OF_IO_RETRY_ATTEMPTS))
                {
                    return Task.FromResult(ConversionError.FailedToCopyFile);
                }
                try
                {
                    if (SafeFileManagement.MoveDirectory(copiedTemplatePath, dataPackData.datapackRootPath, Globals.NUMBER_OF_IO_RETRY_ATTEMPTS))
                    {
                        cancellationToken.ThrowIfCancellationRequested();

                        // Must change the folder names before searching for keys
                        string songname_uuidFolder = Path.Combine(dataPackData.datapackRootPath, Globals.DATA, Globals.FOLDER_UUID);
                        string newPath = Path.Combine(dataPackData.datapackRootPath, Globals.DATA, dataPackData.folder_uuid);
                        SafeFileManagement.MoveDirectory(songname_uuidFolder, newPath, Globals.NUMBER_OF_IO_RETRY_ATTEMPTS);

                        // Updating Copied files
                        Filemanagement.UpdateAllCopiedFiles(dataPackData.datapackRootPath, dataPackData.keyVars, true, Globals.excludeKeyVarExtensions);

                        // Copying Image Icon
                        string mapIcon = Path.Combine(unzippedFolderPath, beatSaberMap.InfoData.CoverImageFilename);
                        string packIcon = Path.Combine(dataPackData.datapackRootPath, Globals.PACK_ICON);
                        SafeFileManagement.CopyFileTo(mapIcon, packIcon, true, Globals.NUMBER_OF_IO_RETRY_ATTEMPTS);

                        cancellationToken.ThrowIfCancellationRequested();

                        progress.Report(new ConversionProgress(0.4f, "Generating main datapack files"));
                        var mcBeatDataError = GenerateMCBeatData(beatSaberMap, dataPackData, progress, cancellationToken);
                        if (mcBeatDataError != ConversionError.None)
                        {
                            return Task.FromResult(mcBeatDataError);
                        }
                        cancellationToken.ThrowIfCancellationRequested();

                        progress.Report(new ConversionProgress(0.9f, "Zipping files"));
                        Archive.Compress(dataPackData.datapackRootPath, dataPackData.fullOutputPath, true);
                        return Task.FromResult(ConversionError.None);
                    }
                }
                catch (OperationCanceledException wasCanceled)
                {
                    throw wasCanceled;
                }
                catch (ObjectDisposedException wasAreadyCanceled)
                {
                    throw wasAreadyCanceled;
                }
                return Task.FromResult(ConversionError.OtherFail);
            }));
        }
        /// <summary>
        /// Generate note commands given a song and difficulty
        /// </summary>
        /// <param name="song">Beatmap data for a song and difficulty</param>
        /// <param name="difficultyName">Minecraft safe difficulty name</param>
        /// <param name="commandBasePath">Base folder path to generate new mcfunctions</param>
        /// <param name="packInfo">Beat Saber Parsed info</param>
        /// <param name="dpd">Data used for datapack generation</param>
        public static void GenerateNotes(MapData song, string difficultyName, string commandBasePath, Info packInfo, DataPackData dpd)
        {
            double prevNodeTime            = 0;
            int    nodeRowID               = 1;
            int    currentLevel            = 1;
            int    currentTick             = 0;
            int    prevCurrentTick         = 0;
            int    currentNumberOfCommands = 0;
            int    noteIndex               = 0;
            int    currentCommandLimit     = Globals.COMMAND_LIMIT;

            var notes = song.Notes;

            // Main note generation
            while (noteIndex < notes.Length)
            {
                string        commandLevelName     = difficultyName + Globals.LEVEL_NOTE_NAME + currentLevel;
                string        commandLevelFileName = commandLevelName + Globals.MCFUNCTION;
                string        commandLevelFilePath = Path.Combine(dpd.folder_uuidFunctionsPath, commandLevelFileName);
                StringBuilder currentCommands      = new StringBuilder();

                // Continue to generate commands until all nodes and obstacles have been iterated though
                while (noteIndex < notes.Length && currentNumberOfCommands < currentCommandLimit)
                {
                    if (prevNodeTime != notes[noteIndex].Time)
                    {
                        nodeRowID++;
                    }

                    NodeDataToCommands(notes[noteIndex], packInfo.BeatsPerMinute, dpd.metersPerTick, nodeRowID, ref currentCommands, ref currentTick);

                    prevNodeTime             = notes[noteIndex].Time;
                    currentNumberOfCommands += 3;
                    noteIndex++;
                }

                if (noteIndex >= notes.Length)
                {
                    currentTick += (int)(dpd.ticksStartOffset + 1);;
                    currentCommands.AppendFormat(Globals.templateStrings._finishedNotes, currentTick);
                }

                SafeFileManagement.SetFileContents(commandLevelFilePath, currentCommands.ToString());
                string baseCommand = string.Format(Globals.templateStrings._baseCommand,
                                                   prevCurrentTick,
                                                   currentTick,
                                                   dpd.folder_uuid,
                                                   commandLevelName);
                SafeFileManagement.AppendFile(commandBasePath, baseCommand);
                prevCurrentTick     = currentTick + 1;
                currentCommandLimit = currentNumberOfCommands + Globals.COMMAND_LIMIT;
                currentLevel++;
            }

            // Note pretty buy create a command if no obstacles are present in a map
            if (notes.Length == 0)
            {
                currentTick += (int)(dpd.ticksStartOffset + 1);;
                string        commandLevelName     = difficultyName + Globals.LEVEL_NOTE_NAME + currentLevel;
                string        commandLevelFileName = commandLevelName + Globals.MCFUNCTION;
                string        commandLevelFilePath = Path.Combine(dpd.folder_uuidFunctionsPath, commandLevelFileName);
                StringBuilder currentCommands      = new StringBuilder();
                SafeFileManagement.SetFileContents(commandLevelFilePath, currentCommands.ToString());
                string baseCommand = string.Format(Globals.templateStrings._baseCommand,
                                                   prevCurrentTick,
                                                   currentTick,
                                                   dpd.folder_uuid,
                                                   commandLevelName);
                SafeFileManagement.AppendFile(commandBasePath, baseCommand);
                currentCommands.AppendFormat(Globals.templateStrings._finishedNotes, currentTick);
            }
        }
        public static void GenerateEvents(MapData song, string difficultyName, string commandBasePath, Info packInfo, DataPackData dpd)
        {
            int currentLevel            = 1;
            int currentTick             = 0;
            int prevCurrentTick         = 0;
            int currentNumberOfCommands = 0;
            int noteIndex           = 0;
            int currentCommandLimit = Globals.COMMAND_LIMIT;

            var bEvents = song.Events.Where(x => x.Value >= 0 && x.Value <= 11).ToArray();

            List <EventFadeSave> autoOffTick = new List <EventFadeSave>();

            for (int i = 0; i < 10; i++)
            {
                autoOffTick.Add(new EventFadeSave());
            }
            // Main note generation
            while (noteIndex < bEvents.Length)
            {
                string        commandLevelName     = difficultyName + Globals.LEVEL_EVENT_NAME + currentLevel;
                string        commandLevelFileName = commandLevelName + Globals.MCFUNCTION;
                string        commandLevelFilePath = Path.Combine(dpd.folder_uuidFunctionsPath, commandLevelFileName);
                StringBuilder currentCommands      = new StringBuilder();

                // Continue to generate commands until all events
                while (noteIndex < bEvents.Length && currentNumberOfCommands < currentCommandLimit)
                {
                    currentNumberOfCommands += EventDataToCommands(bEvents[noteIndex], packInfo.BeatsPerMinute, dpd, ref currentCommands, ref currentTick, ref autoOffTick);
                    noteIndex++;
                }

                if (noteIndex >= bEvents.Length)
                {
                    currentTick += (int)(dpd.ticksStartOffset + 1);;
                    currentCommands.AppendFormat(Globals.templateStrings._finishedEvents, currentTick);
                }

                SafeFileManagement.SetFileContents(commandLevelFilePath, currentCommands.ToString());
                string baseCommand = string.Format(Globals.templateStrings._baseCommand,
                                                   prevCurrentTick,
                                                   currentTick,
                                                   dpd.folder_uuid,
                                                   commandLevelName);
                SafeFileManagement.AppendFile(commandBasePath, baseCommand);
                prevCurrentTick     = currentTick + 1;
                currentCommandLimit = currentNumberOfCommands + Globals.COMMAND_LIMIT;
                currentLevel++;
            }


            // Note pretty buy create a command if no obstacles are present in a map
            if (bEvents.Length == 0)
            {
                currentTick += (int)(dpd.ticksStartOffset + 1);;
                string        commandLevelName     = difficultyName + Globals.LEVEL_EVENT_NAME + currentLevel;
                string        commandLevelFileName = commandLevelName + Globals.MCFUNCTION;
                string        commandLevelFilePath = Path.Combine(dpd.folder_uuidFunctionsPath, commandLevelFileName);
                StringBuilder currentCommands      = new StringBuilder();
                SafeFileManagement.SetFileContents(commandLevelFilePath, currentCommands.ToString());
                string baseCommand = string.Format(Globals.templateStrings._baseCommand,
                                                   prevCurrentTick,
                                                   currentTick,
                                                   dpd.folder_uuid,
                                                   commandLevelName);
                SafeFileManagement.AppendFile(commandBasePath, baseCommand);
                currentCommands.AppendFormat(Globals.templateStrings._finishedNotes, currentTick);
            }
        }
        /// <summary>
        /// Main generation of minecraft commands for beat saber data
        /// </summary>
        /// <param name="beatMapSongList">List of Beat Saber song data</param>
        /// <param name="packInfo">Beat Saber Parsed info</param>
        /// <param name="dpd">Data used for datapack generation</param>
        /// <returns></returns>
        public static ConversionError GenerateMCBeatData(BeatSaberMap beatSaberMap, DataPackData dpd, IProgress <ConversionProgress> progress, CancellationToken cancellationToken)
        {
            StringBuilder difficultyDisplayCommands = new StringBuilder();
            StringBuilder scoreboardCommands        = new StringBuilder();
            StringBuilder spawnOriginCommands       = new StringBuilder();
            StringBuilder spawnNotesBaseCommands    = new StringBuilder();
            int           difficultyNumber          = 1;

            // Iterate though each song difficulty
            var mapDataInfos      = beatSaberMap.MapDataInfos;
            var progressPercent   = 0.4f;
            var mapDataInfosKeys  = mapDataInfos.Keys.ToArray();
            var progressIncrament = mapDataInfosKeys.Length * 0.5f;

            for (int i = 0; i < mapDataInfosKeys.Length; i++)
            {
                var mapDataInfo = mapDataInfos[mapDataInfosKeys[i]];
                if (mapDataInfo.MapData == null || mapDataInfo.MapData.Notes == null || mapDataInfo.MapData.Obstacles == null)
                {
                    return(ConversionError.NoMapData);
                }
                progress.Report(new ConversionProgress(progressPercent + progressIncrament, "Generating minecraft commands"));
                if (mapDataInfo.MapData.Notes.Length > 0 || mapDataInfo.MapData.Obstacles.Length > 0)
                {
                    string difficultyName = mapDataInfo.DifficultyBeatmapInfo.Difficulty.MakeMinecraftSafe();

                    // Append running command lists
                    string songDifficultyID = dpd.songGuid + difficultyNumber.ToString();
                    scoreboardCommands.AppendFormat(Globals.templateStrings._scoreboardCommand, songDifficultyID);
                    spawnOriginCommands.AppendFormat(Globals.templateStrings._spawnOriginCommands, -dpd.metersPerTick * dpd.ticksStartOffset, difficultyNumber);
                    spawnNotesBaseCommands.AppendFormat(Globals.templateStrings._spawnNotesBaseCommand, difficultyNumber, dpd.folder_uuid, difficultyName);

                    CreateDifficultyDisplay(songDifficultyID, difficultyName, dpd.folder_uuid, ref difficultyDisplayCommands);

                    // Write difficulty-specific-file commands
                    string playCommands = string.Format(Globals.templateStrings._playCommands, difficultyNumber, dpd.folder_uuid);
                    string playPath     = Path.Combine(dpd.folder_uuidFunctionsPath, difficultyName + "_play" + Globals.MCFUNCTION);
                    SafeFileManagement.SetFileContents(playPath, playCommands);

                    string playSongCommand = string.Format(Globals.templateStrings._playSongCommand, dpd.ticksStartOffset - 1, dpd.folder_uuid);
                    string commandBasePath = Path.Combine(dpd.folder_uuidFunctionsPath, difficultyName + Globals.MCFUNCTION);
                    SafeFileManagement.AppendFile(commandBasePath, playSongCommand);

                    string completedSongCommand = string.Format(Globals.templateStrings._completedSong, difficultyNumber, songDifficultyID);
                    string completedSongPath    = Path.Combine(dpd.folder_uuidFunctionsPath, Globals.MAP_DIFFICULTY_COMPLETED);
                    SafeFileManagement.AppendFile(completedSongPath, completedSongCommand);

                    // Generate main note/obstacle data/light data
                    GenerateNotes(mapDataInfo.MapData, difficultyName, commandBasePath, beatSaberMap.InfoData, dpd);
                    GenerateObstacles(mapDataInfo.MapData, difficultyName, commandBasePath, beatSaberMap.InfoData, dpd);
                    GenerateEvents(mapDataInfo.MapData, difficultyName, commandBasePath, beatSaberMap.InfoData, dpd);

                    difficultyNumber++;
                }
            }

            // Write collected commands to files
            string difficultiesFunctionPath  = Path.Combine(dpd.folder_uuidFunctionsPath, Globals.DIFFICULTIES);
            string initFunctionPath          = Path.Combine(dpd.folder_uuidFunctionsPath, Globals.INIT_FUNCTION);
            string setSpawnOrginFunctionPath = Path.Combine(dpd.folder_uuidFunctionsPath, Globals.SET_SPAWN_ORIGIN);

            SafeFileManagement.AppendFile(dpd.spawnNotesBasePath, spawnNotesBaseCommands.ToString());
            SafeFileManagement.AppendFile(setSpawnOrginFunctionPath, spawnOriginCommands.ToString());
            SafeFileManagement.AppendFile(initFunctionPath, scoreboardCommands.ToString());

            // Add back button in tellraw
            difficultyDisplayCommands.Append(Globals.templateStrings._mainMenuBack);
            SafeFileManagement.AppendFile(difficultiesFunctionPath, difficultyDisplayCommands.ToString());
            return(ConversionError.None);
        }