using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Threading.Tasks; using Ichni.Editor; using Ichni.RhythmGame; using Ichni.RhythmGame.Beatmap; using UnityEngine; namespace Ichni { public class ProjectManager { public static readonly ES3Settings SaveSettings = new ES3Settings { compressionType = ES3.CompressionType.None, encryptionType = ES3.EncryptionType.None, format = ES3.Format.JSON, }; public static readonly ES3Settings ExportSettings = new ES3Settings { compressionType = ES3.CompressionType.Gzip, encryptionType = ES3.EncryptionType.AES, encryptionPassword = "Soullies515", format = ES3.Format.JSON, }; public SaveManager saveManager; public LoadManager loadManager; public ExportManager exportManager; public BeatmapClipManager beatmapClipManager; public BeatmapMergeManager beatmapMergeManager; public NotePrefabManager notePrefabManager; public AutoSaveManager autoSaveManager; public ProjectManager() { saveManager = new SaveManager(); loadManager = new LoadManager(); exportManager = new ExportManager(); beatmapClipManager = new BeatmapClipManager(); beatmapMergeManager = new BeatmapMergeManager(); notePrefabManager = new NotePrefabManager(); autoSaveManager = new AutoSaveManager(); } public void GenerateEmptyProject(ProjectInformation_BM projectInfo_BM, SongInformation_BM songInfo_BM) { projectInfo_BM.ExecuteBM(); songInfo_BM.ExecuteBM(); EditorManager.instance.beatmapContainer = new BeatmapContainer(); EditorManager.instance.commandScripts = new CommandScripts(new List()); } } public class ExportManager { public void Export() { string exportPath = Application.streamingAssetsPath + "/Export/" + EditorManager.instance.projectInformation.projectName; string projectInfoPath = exportPath + "/ProjectInfo.bytes"; string songInfoPath = exportPath + "/SongInfo.bytes"; string beatmapPath = exportPath + "/Beatmap.bytes"; string commandScriptsPath = exportPath + "/CommandScripts.bytes"; LogWindow.Log("Start Exporting..."); ExportProjectInfo(projectInfoPath); ExportSongInfo(songInfoPath); ExportBeatMap(beatmapPath); ExportCommandScripts(commandScriptsPath); LogWindow.Log("Export Complete", Color.green); } private void ExportProjectInfo(string exportPath) { EditorManager.instance.projectInformation.SaveBM(); ES3.Save("ProjectInformation", EditorManager.instance.projectInformation.matchedBM as ProjectInformation_BM, exportPath, ProjectManager.ExportSettings); } private void ExportSongInfo(string exportPath) { EditorManager.instance.songInformation.SaveBM(); ES3.Save("SongInformation", EditorManager.instance.songInformation.matchedBM as SongInformation_BM, exportPath, ProjectManager.ExportSettings); } private void ExportBeatMap(string exportPath) { EditorManager.instance.beatmapContainer.SaveExportBM(); ES3.Save("Beatmap", EditorManager.instance.beatmapContainer.MatchingExportElement as BeatmapContainer_BM, exportPath, ProjectManager.ExportSettings); } private void ExportCommandScripts(string exportPath) { EditorManager.instance.commandScripts.SaveBM(); ES3.Save("CommandScripts", EditorManager.instance.commandScripts.matchedBM as CommandScripts_BM, exportPath, ProjectManager.ExportSettings); } } public class SaveManager { public void Save() { LogWindow.Log("Start Saving..."); _ = SaveAllCoroutine(); } public async Task SaveAllCoroutine() { await Task.Run(() => { SaveProjectInfo(); SaveSongInfo(); SaveBeatMap(); SaveCommandScripts(); }); LogWindow.Log("Save Complete", Color.green); } private void SaveProjectInfo() { EditorManager.instance.projectInformation.SaveBM(); ES3.Save("ProjectInformation", EditorManager.instance.projectInformation.matchedBM as ProjectInformation_BM, EditorManager.instance.projectInformation.peojectInfoPath, ProjectManager.SaveSettings); } private void SaveSongInfo() { EditorManager.instance.songInformation.SaveBM(); ES3.Save("SongInformation", EditorManager.instance.songInformation.matchedBM as SongInformation_BM, EditorManager.instance.projectInformation.songInfoPath, ProjectManager.SaveSettings); } private void SaveBeatMap() { EditorManager.instance.beatmapContainer.SaveBM(); ES3.Save("Beatmap", EditorManager.instance.beatmapContainer.matchedBM as BeatmapContainer_BM, EditorManager.instance.projectInformation.beatmapPath, ProjectManager.SaveSettings); } private void SaveCommandScripts() { EditorManager.instance.commandScripts.SaveBM(); ES3.Save("CommandScripts", EditorManager.instance.commandScripts.matchedBM as CommandScripts_BM, EditorManager.instance.projectInformation.CommandScriptsPath, ProjectManager.SaveSettings); } } public class LoadManager { public void Load(string projectName) { LoadProjectInfo(projectName); LoadSongInfo(); LoadCommandScripts(); LoadBeatMap(); LogWindow.Log("Load Complete", Color.green); } private void LoadProjectInfo(string projectName) { string projectInfoPath = Application.streamingAssetsPath + "/Projects/" + projectName + "/ProjectInfo.json"; ES3.Load("ProjectInformation", projectInfoPath, ProjectManager.SaveSettings).ExecuteBM(); } private void LoadSongInfo() { ES3.Load("SongInformation", EditorManager.instance.projectInformation.songInfoPath, ProjectManager.SaveSettings).ExecuteBM(); } private void LoadBeatMap() { ES3.Load("Beatmap", EditorManager.instance.projectInformation.beatmapPath, ProjectManager.SaveSettings).ExecuteBM(); } private void LoadCommandScripts() { ES3.Load("CommandScripts", EditorManager.instance.projectInformation.CommandScriptsPath, ProjectManager.SaveSettings).ExecuteBM(); } public void LoadExport(string projectName) { LoadProjectInfoExport(projectName); LoadSongInfoExport(projectName); LoadCommandScriptsExport(projectName); LoadBeatMapExport(projectName); LogWindow.Log("Load Export Complete,", new Color(0.5f, 0f, 1f)); LogWindow.Log("Please Save Your Project As You Can", new Color(0.5f, 0f, 1f)); } private void LoadProjectInfoExport(string projectName) { string projectInfoPath = Application.streamingAssetsPath + "/Export/" + projectName + "/ProjectInfo.bytes"; ES3.Load("ProjectInformation", projectInfoPath, ProjectManager.ExportSettings).ExecuteBM(); } private void LoadSongInfoExport(string projectName) { string songInfoPath = Application.streamingAssetsPath + "/Export/" + projectName + "/SongInfo.bytes"; ES3.Load("SongInformation", songInfoPath, ProjectManager.ExportSettings).ExecuteBM(); } private void LoadBeatMapExport(string projectName) { string beatMapPath = Application.streamingAssetsPath + "/Export/" + projectName + "/Beatmap.bytes"; ES3.Load("Beatmap", beatMapPath, ProjectManager.ExportSettings).ExecuteBM(); } private void LoadCommandScriptsExport(string projectName) { string commandScriptsPath = Application.streamingAssetsPath + "/Export/" + projectName + "/CommandScripts.bytes"; ES3.Load("CommandScripts", commandScriptsPath, ProjectManager.ExportSettings).ExecuteBM(); } } public class BeatmapClipManager { public void SaveClip(string clipName) { LogWindow.Log("Start Saving Clip..."); if (EditorManager.instance.operationManager.currentSelectedElements.Count != 1) // TODO: 后续适应多选 { LogWindow.Log("Please select only one Game Element to save the beatmap clip.", Color.red); return; } GameElement selectedElement = EditorManager.instance.operationManager.currentSelectedElements[0]; if (selectedElement is null) { LogWindow.Log("Please select a Game Element to save the beatmap clip.", Color.red); return; } _SaveClip(selectedElement, clipName); LogWindow.Log("Save Clip Complete", Color.green); } public void LoadClip(string clipName) { LogWindow.Log("Start Loading Clip..."); if (!ES3.FileExists(Application.streamingAssetsPath + "/Clips/" + clipName + ".json")) { LogWindow.Log("Clip not found", Color.red); return; } if (EditorManager.instance.operationManager.currentSelectedElements.Count != 1) // TODO: 后续适应多选 { LogWindow.Log("Please select only one Game Element to load the beatmap clip.", Color.red); return; } GameElement selectedElement = EditorManager.instance.operationManager.currentSelectedElements[0]; if (selectedElement is null) { LogWindow.Log("Please select a Game Element to load the beatmap clip.", Color.red); return; } Debug.Log(selectedElement.elementName + " " + selectedElement.elementGuid); _LoadClip(selectedElement, clipName); LogWindow.Log("Load Clip Complete", Color.green); } private void _SaveClip(GameElement element, string clipName) { List clip = new List(); element.GetAllGameElementsFromThis().ForEach(e => { e.SaveBM(); clip.Add(e.matchedBM); e.submoduleList.ForEach(s => { s.SaveBM(); if (s.matchedBM != null) clip.Add(s.matchedBM); }); }); string filePath = Application.streamingAssetsPath + "/Clips/" + clipName + ".json"; ES3.Save("Clip", clip, filePath, ProjectManager.SaveSettings); } private void _LoadClip(GameElement target, string clipName) { try { string filePath = Application.streamingAssetsPath + "/Clips/" + clipName + ".json"; List clip = ES3.Load>("Clip", filePath, ProjectManager.SaveSettings); if (clip == null || clip.Count == 0) { Debug.LogError("Clip is empty or null"); return; } // 验证第一个元素 if (!(clip[0] is GameElement_BM first)) { Debug.LogError("First element is not a GameElement_BM"); return; } // 处理目标对象 - 添加清理机制 target.SaveBM(); bool targetAdded = GameElement_BM.identifier.TryAdd(target.elementGuid, target.matchedBM as GameElement_BM); if (!targetAdded) { Debug.LogWarning("Target element already exists in identifier dictionary"); } // 处理第一个元素及其附加元素 ProcessGameElement(first, clip, target.elementGuid); // 处理后续元素 for (var index = 1; index < clip.Count; index++) { if (clip[index] is GameElement_BM gameElement) { ProcessGameElement(gameElement, clip, null); } else { // 非GameElement_BM直接执行 clip[index].ExecuteBM(); } } } catch (Exception ex) { Debug.LogError($"Failed to load clip: {ex.Message}"); } } private void ProcessGameElement(GameElement_BM gameElement, List clip, Guid? attachToGuid) { List attachedElements = GameElement_BM.GetAllAttachedBaseElements(gameElement, clip); // 生成新GUID并验证 gameElement.elementGuid = Guid.NewGuid(); bool elementAdded = GameElement_BM.identifier.TryAdd(gameElement.elementGuid, gameElement); if (!elementAdded) { Debug.LogError($"Failed to add element with GUID: {gameElement.elementGuid}"); return; } // 更新附加元素 attachedElements.ForEach(e => { e.attachedElementGuid = gameElement.elementGuid; }); // 如果需要附加到其他元素 if (attachToGuid.HasValue) { gameElement.attachedElementGuid = attachToGuid.Value; } gameElement.ExecuteBM(); } } public class BeatmapMergeManager { public void MergeBeatmap(string mergeName) { string mergePath = Application.streamingAssetsPath + "/Merges/" + mergeName + ".json"; BeatmapContainer_BM merge = ES3.Load("Beatmap", mergePath, ProjectManager.SaveSettings); merge.elementList.ForEach(element => { if (element == null) { Debug.LogError("Null element detected in elementList. Skipping execution."); return; } if (BeatmapContainer_BM.LowPriorityGameElementTypes.Contains(element.GetType())) { return; } if (element is GameElement_BM gameElement) { GameElement_BM.identifier.Add(gameElement.elementGuid, gameElement); } if (element is GameCamera_BM || GameElement_BM.GetElementBM(element.attachedElementGuid) is GameCamera_BM) { return; } element.ExecuteBM(); }); merge.elementList.ForEach(element => { if (element == null) { Debug.LogError("Null element detected in elementList during low-priority execution. Skipping execution."); return; } if (BeatmapContainer_BM.LowPriorityGameElementTypes.Contains(element.GetType())) { element.ExecuteBM(); } }); EditorManager.instance.beatmapContainer.ExecuteLowPriorityActions(); } } public class NotePrefabManager { private string notePrefabPath => Application.streamingAssetsPath + "/NotePrefabs"; private string GetNotePrefabPath(string notePrefabName) => notePrefabPath + "/" + notePrefabName + ".json"; public void SaveNotePrefab(NoteBase note, string noteName) { List clip = new List(); note.GetAllGameElementsFromThis().ForEach(e => { e.SaveBM(); clip.Add(e.matchedBM); e.submoduleList.ForEach(s => { s.SaveBM(); if (s.matchedBM != null) { clip.Add(s.matchedBM); } }); }); ES3.Save("Note", clip, GetNotePrefabPath(noteName), ProjectManager.SaveSettings); } public void LoadNotePrefab(NoteBase target, string noteName) { if (target.noteVisual != null) { return; } List clip = ES3.Load>("Note", GetNotePrefabPath(noteName), ProjectManager.SaveSettings); if (clip == null || clip.Count == 0) { LogWindow.Log("Note prefab not found", Color.red); return; } target.SaveBM(); GameElement_BM.identifier.TryAdd(target.elementGuid, target.matchedBM as GameElement_BM); (target.matchedBM as GameElement_BM).matchedElement = target; GameElement_BM first = clip[0] as GameElement_BM; List firstAttaches = GameElement_BM.GetAllAttachedBaseElements(first, clip); first.elementGuid = target.elementGuid; GameElement_BM.identifier.TryAdd(first.elementGuid, first); firstAttaches.ForEach(e => { e.attachedElementGuid = first.elementGuid; }); for (var index = 1; index < clip.Count; index++) { var element = clip[index]; if (element is GameElement_BM gameElement) { List attachedElements = GameElement_BM.GetAllAttachedBaseElements(gameElement, clip); gameElement.elementGuid = Guid.NewGuid(); GameElement_BM.identifier.TryAdd(gameElement.elementGuid, gameElement); attachedElements.ForEach(e => { e.attachedElementGuid = gameElement.elementGuid; }); } } for (var index = 1; index < clip.Count; index++) { clip[index].ExecuteBM(); } target.SetDefaultSubmodules(); } } public class AutoSaveManager { private string autoSavePath => Application.streamingAssetsPath + "/AutoSave/" + EditorManager.instance.projectInformation.projectName; private string GetAutoSavePath(string autoSaveName) => autoSavePath + "/" + autoSaveName + ".json"; private float autoSaveInterval => EditorManager.instance.editorSettings.autoSaveInterval; private int maximumAutoSaveCount => EditorManager.instance.editorSettings.maximumAutoSaveCount; public float autoSaveTimer; public AutoSaveManager() { autoSaveTimer = 0; } public void UpdateAutoSave() { autoSaveTimer += Time.deltaTime; if (autoSaveTimer >= autoSaveInterval) { AutoSave(); autoSaveTimer = 0; } } private void AutoSave() { List saveFiles = GetSortedSaveFiles(); if (saveFiles.Count > 0) { // 删除最旧的存档(如果超过数量) if (saveFiles.Count >= maximumAutoSaveCount) { string oldestSave = saveFiles[saveFiles.Count - 1]; File.Delete(oldestSave); saveFiles.RemoveAt(saveFiles.Count - 1); LogWindow.Log("AutoSaving... , the oldest file deleted"); } else { LogWindow.Log("AutoSaving..."); } // 依次重命名存档 for (int i = saveFiles.Count - 1; i >= 0; i--) { string oldPath = saveFiles[i]; string newPath = GetAutoSavePath($"AutoSave_{i + 1}"); File.Move(oldPath, newPath); } } // 保存最新存档 string newestSavePath = GetAutoSavePath("AutoSave_0"); SaveBeatMap(newestSavePath); } private void SaveBeatMap(string autoSavePath) { SaveBeatMapCoroutine(autoSavePath); } async void SaveBeatMapCoroutine(string autoSavePath) { await Task.Run(() => { EditorManager.instance.beatmapContainer.SaveBM(); ES3.Save("Beatmap", EditorManager.instance.beatmapContainer.matchedBM as BeatmapContainer_BM, autoSavePath, ProjectManager.SaveSettings); }); LogWindow.Log("Auto-saved", Color.green); } private List GetSortedSaveFiles() { if (!ES3.DirectoryExists(autoSavePath)) { Directory.CreateDirectory(autoSavePath); } List saveFiles = new List(Directory.GetFiles(autoSavePath, "AutoSave_*.json")); saveFiles.Sort(string.Compare); return saveFiles; } } }