From e1e527ee2b5b0152c20483ae32ed0038ae79458f Mon Sep 17 00:00:00 2001 From: Jonko <69772986+jonko0493@users.noreply.github.com> Date: Thu, 22 Jun 2023 01:10:28 -0700 Subject: [PATCH] Add a bunch of exception handling (#253) --- src/SerialLoops.Lib/Build.cs | 119 +- src/SerialLoops.Lib/Config.cs | 14 +- src/SerialLoops.Lib/IO.cs | 134 +- src/SerialLoops.Lib/Items/ScriptItem.cs | 49 +- src/SerialLoops.Lib/Patch.cs | 23 +- src/SerialLoops.Lib/Project.cs | 541 +++-- src/SerialLoops.Lib/SerialLoops.Lib.csproj | 2 +- src/SerialLoops/Dialogs/AsmHacksDialog.cs | 14 +- .../Dialogs/ProjectCreationDialog.eto.cs | 2 +- src/SerialLoops/Editors/ScriptEditor.cs | 1873 ++++++++--------- src/SerialLoops/Editors/VoicedLineEditor.cs | 4 +- src/SerialLoops/MainForm.eto.cs | 2 +- src/SerialLoops/Utility/LoopyLogger.cs | 6 + src/SerialLoops/Utility/Shared.cs | 98 +- test/SerialLoops.Tests/CoreTests.cs | 12 +- test/SerialLoops.Tests/StartUpTests.cs | 2 +- 16 files changed, 1632 insertions(+), 1263 deletions(-) diff --git a/src/SerialLoops.Lib/Build.cs b/src/SerialLoops.Lib/Build.cs index cb57ebdc..e1e273b4 100644 --- a/src/SerialLoops.Lib/Build.cs +++ b/src/SerialLoops.Lib/Build.cs @@ -74,13 +74,13 @@ private static bool DoBuild(string directory, Project project, Config config, IL commandsIncSb.AppendLine(command.GetMacro()); } - tracker.Focus("Compressing Archives (dat.bin)", 3); + tracker.Focus("Loading Archives (dat.bin)", 3); var dat = ArchiveFile.FromFile(Path.Combine(directory, "original", "archives", "dat.bin"), log); tracker.Finished++; - tracker.CurrentlyLoading = "Compressing Archives (evt.bin)"; + tracker.CurrentlyLoading = "Loading Archives (evt.bin)"; var evt = ArchiveFile.FromFile(Path.Combine(directory, "original", "archives", "evt.bin"), log); tracker.Finished++; - tracker.CurrentlyLoading = "Compressing Archives (grp.bin)"; + tracker.CurrentlyLoading = "Loading Archives (grp.bin)"; var grp = ArchiveFile.FromFile(Path.Combine(directory, "original", "archives", "grp.bin"), log); if (dat is null || evt is null || grp is null) @@ -115,7 +115,7 @@ private static bool DoBuild(string directory, Project project, Config config, IL { if (Path.GetExtension(file).Equals(".png", StringComparison.OrdinalIgnoreCase)) { - ReplaceSingleGraphicsFile(grp, file, index); + ReplaceSingleGraphicsFile(grp, file, index, log); } else if (file.EndsWith("_pal.csv", StringComparison.OrdinalIgnoreCase)) { @@ -216,51 +216,74 @@ private static void CopyToArchivesToIterativeOriginal(string newDataDir, string tracker.Finished+= 3; } - private static void ReplaceSingleGraphicsFile(ArchiveFile grp, string filePath, int index) + private static void ReplaceSingleGraphicsFile(ArchiveFile grp, string filePath, int index, ILogger log) { - GraphicsFile grpFile = grp.Files.FirstOrDefault(f => f.Index == index); - - if (index == 0xE50) + try { - grpFile.InitializeFontFile(); - } + GraphicsFile grpFile = grp.Files.FirstOrDefault(f => f.Index == index); - string paletteFile = Path.Combine(Path.GetDirectoryName(filePath), $"{Path.GetFileNameWithoutExtension(filePath)}_pal.csv"); - if (File.Exists(paletteFile)) - { - grpFile.SetPalette(File.ReadAllText(paletteFile).Split(',').Select(c => SKColor.Parse(c)).ToList()); - } + if (index == 0xE50) + { + grpFile.InitializeFontFile(); + } + + string paletteFile = Path.Combine(Path.GetDirectoryName(filePath), $"{Path.GetFileNameWithoutExtension(filePath)}_pal.csv"); + if (File.Exists(paletteFile)) + { + grpFile.SetPalette(File.ReadAllText(paletteFile).Split(',').Select(c => SKColor.Parse(c)).ToList()); + } - grpFile.SetImage(filePath); + grpFile.SetImage(filePath); - grp.Files[grp.Files.IndexOf(grpFile)] = grpFile; + grp.Files[grp.Files.IndexOf(grpFile)] = grpFile; + } + catch (Exception ex) + { + log.LogException($"Failed replacing graphics file {index} with file '{filePath}'", ex); + } } private static bool ReplaceSingleSourceFile(ArchiveFile archive, string filePath, int index, string devkitArm, string workingDirectory, ILogger log) { - (string objFile, string binFile) = CompileSourceFile(filePath, devkitArm, workingDirectory, log); - if (!File.Exists(binFile)) + try + { + (string objFile, string binFile) = CompileSourceFile(filePath, devkitArm, workingDirectory, log); + if (!File.Exists(binFile)) + { + log.LogError($"Compiled file {binFile} does not exist!"); + return false; + } + ReplaceSingleFile(archive, binFile, index, log); + File.Delete(objFile); + File.Delete(binFile); + return true; + } + catch (Exception ex) { - log.LogError($"Compiled file {binFile} does not exist!"); + log.LogException($"Failed replacing source file {index} in evt.bin with file '{filePath}'", ex); return false; } - ReplaceSingleFile(archive, binFile, index); - File.Delete(objFile); - File.Delete(binFile); - return true; } private static bool ReplaceSingleSourceFile(ArchiveFile archive, string filePath, int index, string devkitArm, string workingDirectory, ILogger log) { - (string objFile, string binFile) = CompileSourceFile(filePath, devkitArm, workingDirectory, log); - if (!File.Exists(binFile)) + try { - log.LogError($"Compiled file {binFile} does not exist!"); + (string objFile, string binFile) = CompileSourceFile(filePath, devkitArm, workingDirectory, log); + if (!File.Exists(binFile)) + { + log.LogError($"Compiled file {binFile} does not exist!"); + return false; + } + ReplaceSingleFile(archive, binFile, index, log); + File.Delete(objFile); + File.Delete(binFile); + return true; + } + catch (Exception ex) + { + log.LogException($"Failed replacing source file {index} in dat.bin with file '{filePath}'", ex); return false; } - ReplaceSingleFile(archive, binFile, index); - File.Delete(objFile); - File.Delete(binFile); - return true; } private static (string, string) CompileSourceFile(string filePath, string devkitArm, string workingDirectory, ILogger log) @@ -322,19 +345,33 @@ private static (string, string) CompileSourceFile(string filePath, string devkit return (objFile, binFile); } - private static void ReplaceSingleFile(ArchiveFile archive, string filePath, int index) + private static void ReplaceSingleFile(ArchiveFile archive, string filePath, int index, ILogger log) { - EventFile file = archive.Files.FirstOrDefault(f => f.Index == index); - file.Data = File.ReadAllBytes(filePath).ToList(); - file.Edited = true; - archive.Files[archive.Files.IndexOf(file)] = file; + try + { + EventFile file = archive.Files.FirstOrDefault(f => f.Index == index); + file.Data = File.ReadAllBytes(filePath).ToList(); + file.Edited = true; + archive.Files[archive.Files.IndexOf(file)] = file; + } + catch (Exception ex) + { + log.LogException($"Failed replacing source file {index} in evt.bin with file '{filePath}'", ex); + } } - private static void ReplaceSingleFile(ArchiveFile archive, string filePath, int index) + private static void ReplaceSingleFile(ArchiveFile archive, string filePath, int index, ILogger log) { - DataFile file = archive.Files.FirstOrDefault(f => f.Index == index); - file.Data = File.ReadAllBytes(filePath).ToList(); - file.Edited = true; - archive.Files[archive.Files.IndexOf(file)] = file; + try + { + DataFile file = archive.Files.FirstOrDefault(f => f.Index == index); + file.Data = File.ReadAllBytes(filePath).ToList(); + file.Edited = true; + archive.Files[archive.Files.IndexOf(file)] = file; + } + catch (Exception ex) + { + log.LogException($"Failed replacing source file {index} in dat.bin with file '{filePath}'", ex); + } } } } diff --git a/src/SerialLoops.Lib/Config.cs b/src/SerialLoops.Lib/Config.cs index 91576a97..70eaec3f 100644 --- a/src/SerialLoops.Lib/Config.cs +++ b/src/SerialLoops.Lib/Config.cs @@ -50,7 +50,7 @@ public static Config LoadConfig(ILogger log) Config defaultConfig = GetDefault(log); defaultConfig.ValidateConfig(log); defaultConfig.ConfigPath = configJson; - defaultConfig.InitializeHacks(); + defaultConfig.InitializeHacks(log); IO.WriteStringFile(configJson, JsonSerializer.Serialize(defaultConfig), log); return defaultConfig; } @@ -60,7 +60,7 @@ public static Config LoadConfig(ILogger log) Config config = JsonSerializer.Deserialize(File.ReadAllText(configJson)); config.ValidateConfig(log); config.ConfigPath = configJson; - config.InitializeHacks(); + config.InitializeHacks(log); return config; } catch (JsonException exc) @@ -81,12 +81,12 @@ public void ValidateConfig(ILogger log) } } - private void InitializeHacks() + private void InitializeHacks(ILogger log) { if (!Directory.Exists(HacksDirectory)) { Directory.CreateDirectory(HacksDirectory); - IO.CopyFiles(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Sources", "Hacks"), HacksDirectory); + IO.CopyFiles(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Sources", "Hacks"), HacksDirectory, log); File.Copy(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Sources", "hacks.json"), Path.Combine(HacksDirectory, "hacks.json")); } @@ -97,7 +97,7 @@ private void InitializeHacks() IEnumerable missingHacks = builtinHacks.Where(h => !Hacks.Contains(h)); if (missingHacks.Any()) { - IO.CopyFiles(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Sources", "Hacks"), HacksDirectory); + IO.CopyFiles(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Sources", "Hacks"), HacksDirectory, log); Hacks.AddRange(missingHacks); File.WriteAllText(Path.Combine(HacksDirectory, "hacks.json"), JsonSerializer.Serialize(Hacks)); } @@ -105,7 +105,7 @@ private void InitializeHacks() IEnumerable updatedHacks = builtinHacks.Where(h => !Hacks.FirstOrDefault(o => h.Name == o.Name)?.DeepEquals(h) ?? false); if (updatedHacks.Any()) { - IO.CopyFiles(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Sources", "Hacks"), HacksDirectory); + IO.CopyFiles(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Sources", "Hacks"), HacksDirectory, log); foreach (AsmHack updatedHack in updatedHacks) { Hacks[Hacks.FindIndex(h => h.Name == updatedHack.Name)] = updatedHack; @@ -141,7 +141,7 @@ private static Config GetDefault(ILogger log) { emulatorPath = Path.Combine("/snap", "melonds", "current", "usr", "local", "bin", "melonDS"); } - if (!Directory.Exists(emulatorPath)) + if (!Directory.Exists(emulatorPath) && !File.Exists(emulatorPath)) { emulatorPath = ""; log.LogWarning("Valid emulator path not found in config.json."); diff --git a/src/SerialLoops.Lib/IO.cs b/src/SerialLoops.Lib/IO.cs index acb706cb..0e4a92c9 100644 --- a/src/SerialLoops.Lib/IO.cs +++ b/src/SerialLoops.Lib/IO.cs @@ -22,17 +22,24 @@ public IODirectory(string name, IODirectory[] subdirectories, IOFile[] files) Files = files; } - public void Create(string basePath) + public void Create(string basePath, ILogger log) { - string dirPath = Path.Combine(basePath, Name); - Directory.CreateDirectory(dirPath); - foreach (IOFile file in Files) + try { - File.Copy(file.FilePath, Path.Combine(dirPath, file.Name)); + string dirPath = Path.Combine(basePath, Name); + Directory.CreateDirectory(dirPath); + foreach (IOFile file in Files) + { + File.Copy(file.FilePath, Path.Combine(dirPath, file.Name)); + } + foreach (IODirectory subdirectory in Subdirectories) + { + subdirectory.Create(dirPath, log); + } } - foreach (IODirectory subdirectory in Subdirectories) + catch (Exception ex) { - subdirectory.Create(dirPath); + log.LogException($"Failed to create directory on path '{basePath}'", ex); } } } @@ -55,12 +62,20 @@ public IOFile(string path, string name) } } - public static void OpenRom(Project project, string romPath, IProgressTracker tracker) + public static void OpenRom(Project project, string romPath, ILogger log, IProgressTracker tracker) { // Unpack the ROM, creating the two project directories tracker.Focus("Creating Directories", 8); - NdsProjectFile.Create(project.Name, romPath, Path.Combine(project.BaseDirectory, "rom")); - NdsProjectFile.Create(project.Name, romPath, Path.Combine(project.IterativeDirectory, "rom")); + try + { + NdsProjectFile.Create(project.Name, romPath, Path.Combine(project.BaseDirectory, "rom")); + NdsProjectFile.Create(project.Name, romPath, Path.Combine(project.IterativeDirectory, "rom")); + } + catch (Exception ex) + { + log.LogException("Failed to unpack ROM", ex); + return; + } tracker.Finished += 2; // Create our structure for building the ROM @@ -101,80 +116,107 @@ public static void OpenRom(Project project, string romPath, IProgressTracker tra new("scn", Array.Empty(), Array.Empty()), }, Array.Empty()); - originalDirectoryTree.Create(project.BaseDirectory); - originalDirectoryTree.Create(project.IterativeDirectory); - srcDirectoryTree.Create(project.BaseDirectory); - srcDirectoryTree.Create(project.IterativeDirectory); - assetsDirectoryTree.Create(project.BaseDirectory); - assetsDirectoryTree.Create(project.IterativeDirectory); + originalDirectoryTree.Create(project.BaseDirectory, log); + originalDirectoryTree.Create(project.IterativeDirectory, log); + srcDirectoryTree.Create(project.BaseDirectory, log); + srcDirectoryTree.Create(project.IterativeDirectory, log); + assetsDirectoryTree.Create(project.BaseDirectory, log); + assetsDirectoryTree.Create(project.IterativeDirectory, log); tracker.Finished += 6; // Copy out the files we need to build the ROM tracker.Focus("Copying Files", 4); - CopyFiles(Path.Combine(project.BaseDirectory, "rom", "data"), Path.Combine(project.BaseDirectory, "original", "archives"), "*.bin"); + CopyFiles(Path.Combine(project.BaseDirectory, "rom", "data"), Path.Combine(project.BaseDirectory, "original", "archives"), log, "*.bin"); tracker.Finished++; - CopyFiles(Path.Combine(project.IterativeDirectory, "rom", "data"), Path.Combine(project.IterativeDirectory, "original", "archives"), "*.bin"); + CopyFiles(Path.Combine(project.IterativeDirectory, "rom", "data"), Path.Combine(project.IterativeDirectory, "original", "archives"), log, "*.bin"); tracker.Finished++; - CopyFiles(Path.Combine(project.BaseDirectory, "rom", "overlay"), Path.Combine(project.BaseDirectory, "original", "overlay")); + CopyFiles(Path.Combine(project.BaseDirectory, "rom", "overlay"), Path.Combine(project.BaseDirectory, "original", "overlay"), log); tracker.Finished++; - CopyFiles(Path.Combine(project.IterativeDirectory, "rom", "overlay"), Path.Combine(project.IterativeDirectory, "original", "overlay")); + CopyFiles(Path.Combine(project.IterativeDirectory, "rom", "overlay"), Path.Combine(project.IterativeDirectory, "original", "overlay"), log); tracker.Finished++; // We conditionalize these so we can test on a non-copyrighted ROM; this should always be true with real data if (Directory.Exists(Path.Combine(project.BaseDirectory, "rom", "data", "bgm"))) { - CopyFiles(Path.Combine(project.BaseDirectory, "rom", "data", "bgm"), Path.Combine(project.BaseDirectory, "original", "bgm")); - CopyFiles(Path.Combine(project.IterativeDirectory, "rom", "data", "bgm"), Path.Combine(project.IterativeDirectory, "original", "bgm")); - CopyFiles(Path.Combine(project.BaseDirectory, "rom", "data", "vce"), Path.Combine(project.BaseDirectory, "original", "vce")); - CopyFiles(Path.Combine(project.IterativeDirectory, "rom", "data", "vce"), Path.Combine(project.IterativeDirectory, "original", "vce")); + CopyFiles(Path.Combine(project.BaseDirectory, "rom", "data", "bgm"), Path.Combine(project.BaseDirectory, "original", "bgm"), log); + CopyFiles(Path.Combine(project.IterativeDirectory, "rom", "data", "bgm"), Path.Combine(project.IterativeDirectory, "original", "bgm"), log); + CopyFiles(Path.Combine(project.BaseDirectory, "rom", "data", "vce"), Path.Combine(project.BaseDirectory, "original", "vce"), log); + CopyFiles(Path.Combine(project.IterativeDirectory, "rom", "data", "vce"), Path.Combine(project.IterativeDirectory, "original", "vce"), log); } } - public static void CopyFileToDirectories(Project project, string sourceFile, string relativePath) + public static void CopyFileToDirectories(Project project, string sourceFile, string relativePath, ILogger log) { string baseFile = Path.Combine(project.BaseDirectory, relativePath); string iterativeFile = Path.Combine(project.IterativeDirectory, relativePath); - - if (!Directory.Exists(Path.GetDirectoryName(baseFile))) + try { - Directory.CreateDirectory(Path.GetDirectoryName(baseFile)); + if (!Directory.Exists(Path.GetDirectoryName(baseFile))) + { + Directory.CreateDirectory(Path.GetDirectoryName(baseFile)); + } + if (!Directory.Exists(Path.GetDirectoryName(iterativeFile))) + { + Directory.CreateDirectory(Path.GetDirectoryName(iterativeFile)); + } + + File.Copy(sourceFile, baseFile, true); + File.Copy(sourceFile, iterativeFile, true); } - if (!Directory.Exists(Path.GetDirectoryName(iterativeFile))) + catch (Exception ex) { - Directory.CreateDirectory(Path.GetDirectoryName(iterativeFile)); + log.LogException($"Failed copying file '{sourceFile}' to base and iterative directories at path '{relativePath}'", ex); } - - File.Copy(sourceFile, baseFile, true); - File.Copy(sourceFile, iterativeFile, true); } - public static void DeleteFiles(Project project, IEnumerable files) + public static void DeleteFiles(Project project, IEnumerable files, ILogger log) { foreach (string file in files) { - File.Delete(Path.Combine(project.IterativeDirectory, file)); - File.Delete(Path.Combine(project.BaseDirectory, file)); + try + { + File.Delete(Path.Combine(project.IterativeDirectory, file)); + File.Delete(Path.Combine(project.BaseDirectory, file)); + } + catch (Exception ex) + { + log.LogException($"Failed to delete file '{file}'", ex); + } } } - public static void CopyFiles(string sourceDirectory, string destinationDirectory, string filter = "*") + public static void CopyFiles(string sourceDirectory, string destinationDirectory, ILogger log, string filter = "*") { foreach (string file in Directory.GetFiles(sourceDirectory, filter)) { - File.Copy(file, Path.Combine(destinationDirectory, Path.GetFileName(file)), overwrite: true); + try + { + File.Copy(file, Path.Combine(destinationDirectory, Path.GetFileName(file)), overwrite: true); + } + catch (Exception ex) + { + log.LogException($"Failed to copy file '{file}' from '{sourceDirectory}' to '{destinationDirectory}'", ex); + } } } - public static void DeleteFilesKeepDirectories(string sourceDirectory) + public static void DeleteFilesKeepDirectories(string sourceDirectory, ILogger log) { foreach (string directory in Directory.GetDirectories(sourceDirectory, "*", SearchOption.AllDirectories)) { foreach (string file in Directory.GetFiles(directory)) { - File.Delete(file); + try + { + File.Delete(file); + } + catch (Exception ex) + { + log.LogException($"Failed to delete file '{file}' from directory '{sourceDirectory}'", ex); + } } } } @@ -196,9 +238,9 @@ public static bool TryReadStringFile(string file, out string content, ILogger lo content = File.ReadAllText(file); return true; } - catch (IOException exc) + catch (IOException ex) { - log.LogError($"Exception occurred while reading file '{file}' from disk.\n{exc.Message}\n\n{exc.StackTrace}"); + log.LogException($"Exception occurred while reading file '{file}' from disk.", ex); content = string.Empty; return false; } @@ -211,9 +253,9 @@ public static bool WriteStringFile(string file, string str, ILogger log) File.WriteAllText(file, str); return true; } - catch (IOException exc) + catch (IOException ex) { - log.LogError($"Exception occurred while writing file '{file}' to disk.\n{exc.Message}\n\n{exc.StackTrace}"); + log.LogException($"Exception occurred while writing file '{file}' to disk.", ex); return false; } } @@ -225,9 +267,9 @@ public static bool WriteBinaryFile(string file, byte[] bytes, ILogger log) File.WriteAllBytes(file, bytes); return true; } - catch (IOException exc) + catch (IOException ex) { - log.LogError($"Exception occurred while writing file '{file}' to disk.\n{exc.Message}\n\n{exc.StackTrace}"); + log.LogException($"Exception occurred while writing file '{file}' to disk.", ex); return false; } } diff --git a/src/SerialLoops.Lib/Items/ScriptItem.cs b/src/SerialLoops.Lib/Items/ScriptItem.cs index adaeb8a1..8130ed3a 100644 --- a/src/SerialLoops.Lib/Items/ScriptItem.cs +++ b/src/SerialLoops.Lib/Items/ScriptItem.cs @@ -23,22 +23,30 @@ public ScriptItem(EventFile evt, ILogger log) : base(evt.Name[0..^1], ItemType.S { Event = evt; - PruneLabelsSection(); + PruneLabelsSection(log); Graph.AddVertexRange(Event.ScriptSections); } public Dictionary> GetScriptCommandTree(Project project, ILogger log) { - Dictionary> commands = new(); - foreach (ScriptSection section in Event.ScriptSections) + try { - commands.Add(section, new()); - foreach (ScriptCommandInvocation command in section.Objects) + Dictionary> commands = new(); + foreach (ScriptSection section in Event.ScriptSections) { - commands[section].Add(ScriptItemCommand.FromInvocation(command, section, commands[section].Count, Event, project, log)); + commands.Add(section, new()); + foreach (ScriptCommandInvocation command in section.Objects) + { + commands[section].Add(ScriptItemCommand.FromInvocation(command, section, commands[section].Count, Event, project, log)); + } } + return commands; + } + catch (Exception ex) + { + log.LogException($"Error getting script command tree for script {DisplayName} ({Name})", ex); + return null; } - return commands; } public void CalculateGraphEdges(Dictionary> commandTree, ILogger log) @@ -127,22 +135,29 @@ public void CalculateGraphEdges(Dictionary Event.ScriptSections.Count) + if ((Event.LabelsSection?.Objects?.Count ?? 0) - 1 > Event.ScriptSections.Count) { - for (int i = 0; i < Event.LabelsSection.Objects.Count; i++) + try { - if (Event.LabelsSection.Objects[i].Id == 0) - { - continue; - } - if (!Event.ScriptSections.Select(s => s.Name).Contains(Event.LabelsSection.Objects[i].Name.Replace("/", ""))) + for (int i = 0; i < Event.LabelsSection.Objects.Count; i++) { - Event.LabelsSection.Objects.RemoveAt(i); - i--; + if (Event.LabelsSection.Objects[i].Id == 0) + { + continue; + } + if (!Event.ScriptSections.Select(s => s.Name).Contains(Event.LabelsSection.Objects[i].Name.Replace("/", ""))) + { + Event.LabelsSection.Objects.RemoveAt(i); + i--; + } } } + catch (Exception ex) + { + log.LogException($"Error pruning labels for script {DisplayName} ({Name})", ex); + } } } diff --git a/src/SerialLoops.Lib/Patch.cs b/src/SerialLoops.Lib/Patch.cs index 3042df33..4aa7e850 100644 --- a/src/SerialLoops.Lib/Patch.cs +++ b/src/SerialLoops.Lib/Patch.cs @@ -1,17 +1,26 @@ -using System.IO; +using HaruhiChokuretsuLib.Util; +using System; +using System.IO; using VCDiff.Encoders; namespace SerialLoops.Lib { public static class Patch { - public static void CreatePatch(string baseRom, string currentRom, string outputFile) + public static void CreatePatch(string baseRom, string currentRom, string outputFile, ILogger log) { - using FileStream baseRomStream = File.OpenRead(baseRom); - using FileStream currentRomStream = File.OpenRead(currentRom); - using FileStream outputFileStream = File.Create(outputFile); - VcEncoder encoder = new(baseRomStream, currentRomStream, outputFileStream); - encoder.Encode(); + try + { + using FileStream baseRomStream = File.OpenRead(baseRom); + using FileStream currentRomStream = File.OpenRead(currentRom); + using FileStream outputFileStream = File.Create(outputFile); + VcEncoder encoder = new(baseRomStream, currentRomStream, outputFileStream); + encoder.Encode(); + } + catch (Exception ex) + { + log.LogException($"Failed to create patch of base ROM '{baseRom}' from current ROM '{currentRom}' outputting to '{outputFile}'", ex); + } } } } diff --git a/src/SerialLoops.Lib/Project.cs b/src/SerialLoops.Lib/Project.cs index c762da81..65c89f7c 100644 --- a/src/SerialLoops.Lib/Project.cs +++ b/src/SerialLoops.Lib/Project.cs @@ -91,15 +91,15 @@ public Project(string name, string langCode, Config config, ILogger log) try { Directory.CreateDirectory(MainDirectory); - Save(); + Save(log); Directory.CreateDirectory(BaseDirectory); Directory.CreateDirectory(IterativeDirectory); Directory.CreateDirectory(Path.Combine(MainDirectory, "font")); File.Copy(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Sources", "charset.json"), Path.Combine(MainDirectory, "font", "charset.json")); } - catch (Exception exc) + catch (Exception ex) { - log.LogError($"Exception occurred while attempting to create project directories.\n{exc.Message}\n\n{exc.StackTrace}"); + log.LogException("Exception occurred while attempting to create project directories.", ex); } } @@ -140,8 +140,8 @@ public LoadProjectResult Load(Config config, ILogger log, IProgressTracker track string makefile = File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Sources", "Makefile_main")); if (!makefile.Equals(File.ReadAllText(Path.Combine(BaseDirectory, "src", "Makefile")))) { - IO.CopyFileToDirectories(this, Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Sources", "Makefile_main"), Path.Combine("src", "Makefile")); - IO.CopyFileToDirectories(this, Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Sources", "Makefile_overlay"), Path.Combine("src", "overlays", "Makefile")); + IO.CopyFileToDirectories(this, Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Sources", "Makefile_main"), Path.Combine("src", "Makefile"), log); + IO.CopyFileToDirectories(this, Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Sources", "Makefile_overlay"), Path.Combine("src", "overlays", "Makefile"), log); } if (!string.IsNullOrEmpty(config.DevkitArmPath)) { @@ -163,8 +163,16 @@ public LoadProjectResult Load(Config config, ILogger log, IProgressTracker track public void LoadProjectSettings(ILogger log, IProgressTracker tracker) { tracker.Focus("Project Settings", 1); - byte[] projectFile = File.ReadAllBytes(Path.Combine(IterativeDirectory, "rom", $"{Name}.xml")); - Settings = new(NdsProjectFile.FromByteArray(projectFile), log); + string projPath = Path.Combine(IterativeDirectory, "rom", $"{Name}.xml"); + try + { + byte[] projectFile = File.ReadAllBytes(projPath); + Settings = new(NdsProjectFile.FromByteArray(projectFile), log); + } + catch (Exception ex) + { + log.LogException($"Failed to load project from {projPath}", ex); + } tracker.Finished++; } @@ -190,6 +198,11 @@ public LoadProjectResult LoadArchives(ILogger log, IProgressTracker tracker) return new(LoadProjectState.CORRUPTED_FILE, "dat.bin", -1); } } + catch (Exception ex) + { + log.LogException("Error occurred while loading dat.bin", ex); + return new(LoadProjectState.FAILED); + } tracker.Finished++; tracker.CurrentlyLoading = "grp.bin"; @@ -212,6 +225,11 @@ public LoadProjectResult LoadArchives(ILogger log, IProgressTracker tracker) return new(LoadProjectState.CORRUPTED_FILE, "grp.bin", -1); } } + catch (Exception ex) + { + log.LogException("Error occurred while loading grp.bin", ex); + return new(LoadProjectState.FAILED); + } tracker.Finished++; tracker.CurrentlyLoading = "evt.bin"; @@ -234,6 +252,11 @@ public LoadProjectResult LoadArchives(ILogger log, IProgressTracker tracker) return new(LoadProjectState.CORRUPTED_FILE, "evt.bin", -1); } } + catch (Exception ex) + { + log.LogException("Error occurred while loading evt.bin", ex); + return new(LoadProjectState.FAILED); + } tracker.Finished++; string charactersFile = LangCode switch @@ -241,7 +264,15 @@ public LoadProjectResult LoadArchives(ILogger log, IProgressTracker tracker) "ja" => "DefaultCharacters.ja.json", _ => "DefaultCharacters.en.json" }; - Characters ??= JsonSerializer.Deserialize>(File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Defaults", charactersFile)), SERIALIZER_OPTIONS); + try + { + Characters ??= JsonSerializer.Deserialize>(File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Defaults", charactersFile)), SERIALIZER_OPTIONS); + } + catch (Exception ex) + { + log.LogException($"Failed to load DefaultCharacters file", ex); + return new(LoadProjectState.FAILED); + } tracker.Focus("Font", 5); if (IO.TryReadStringFile(Path.Combine(MainDirectory, "font", "charset.json"), out string json, log)) @@ -253,208 +284,409 @@ public LoadProjectResult LoadArchives(ILogger log, IProgressTracker tracker) log.LogError("Failed to load font replacement dictionary."); } tracker.Finished++; - FontMap = Dat.Files.First(f => f.Name == "FONTS").CastTo(); + try + { + FontMap = Dat.Files.First(f => f.Name == "FONTS").CastTo(); + } + catch (Exception ex) + { + log.LogException($"Failed to load font map", ex); + return new(LoadProjectState.FAILED); + } tracker.Finished++; - SpeakerBitmap = Grp.Files.First(f => f.Name == "SYS_CMN_B12DNX").GetImage(transparentIndex: 0); - NameplateBitmap = Grp.Files.First(f => f.Name == "SYS_CMN_B12DNX").GetImage(); + try + { + SpeakerBitmap = Grp.Files.First(f => f.Name == "SYS_CMN_B12DNX").GetImage(transparentIndex: 0); + } + catch (Exception ex) + { + log.LogException($"Failed to load speaker bitmap", ex); + return new(LoadProjectState.FAILED); + } + try + { + NameplateBitmap = Grp.Files.First(f => f.Name == "SYS_CMN_B12DNX").GetImage(); + } + catch (Exception ex) + { + log.LogException($"Failed to load nameplate bitmap", ex); + return new(LoadProjectState.FAILED); + } tracker.Finished++; - DialogueBitmap = Grp.Files.First(f => f.Name == "SYS_CMN_B02DNX").GetImage(transparentIndex: 0); + try + { + DialogueBitmap = Grp.Files.First(f => f.Name == "SYS_CMN_B02DNX").GetImage(transparentIndex: 0); + } + catch (Exception ex) + { + log.LogException($"Failed to load dialogue bitmap", ex); + return new(LoadProjectState.FAILED); + } tracker.Finished++; - GraphicsFile fontFile = Grp.Files.First(f => f.Name == "ZENFONTBNF"); - fontFile.InitializeFontFile(); - FontBitmap = Grp.Files.First(f => f.Name == "ZENFONTBNF").GetImage(transparentIndex: 0); + try + { + GraphicsFile fontFile = Grp.Files.First(f => f.Name == "ZENFONTBNF"); + fontFile.InitializeFontFile(); + FontBitmap = Grp.Files.First(f => f.Name == "ZENFONTBNF").GetImage(transparentIndex: 0); + } + catch (Exception ex) + { + log.LogException($"Failed to load font bitmap", ex); + return new(LoadProjectState.FAILED); + } tracker.Finished++; tracker.Focus("Static Files", 4); - Extra = Dat.Files.First(f => f.Name == "EXTRAS").CastTo(); + try + { + Extra = Dat.Files.First(f => f.Name == "EXTRAS").CastTo(); + } + catch (Exception ex) + { + log.LogException($"Failed to load extra file", ex); + return new(LoadProjectState.FAILED); + } tracker.Finished++; - EventFile scenario = Evt.Files.First(f => f.Name == "SCENARIOS"); - scenario.InitializeScenarioFile(); - Scenario = scenario.Scenario; + try + { + EventFile scenario = Evt.Files.First(f => f.Name == "SCENARIOS"); + scenario.InitializeScenarioFile(); + Scenario = scenario.Scenario; + } + catch (Exception ex) + { + log.LogException($"Failed to load scenario file", ex); + return new(LoadProjectState.FAILED); + } tracker.Finished++; - MessInfo = Dat.Files.First(f => f.Name == "MESSINFOS").CastTo(); + try + { + MessInfo = Dat.Files.First(f => f.Name == "MESSINFOS").CastTo(); + } + catch (Exception ex) + { + log.LogException($"Failed to load message info file", ex); + return new(LoadProjectState.FAILED); + } tracker.Finished++; - UiText = Dat.Files.First(f => f.Name == "MESSS").CastTo(); + try + { + UiText = Dat.Files.First(f => f.Name == "MESSS").CastTo(); + } + catch (Exception ex) + { + log.LogException($"Failed to load UI text file", ex); + return new(LoadProjectState.FAILED); + } tracker.Finished++; - BgTableFile bgTable = Dat.Files.First(f => f.Name == "BGTBLS").CastTo(); - tracker.Focus("Backgrounds", bgTable.BgTableEntries.Count); - for (int i = 0; i < bgTable.BgTableEntries.Count; i++) + try { - BgTableEntry entry = bgTable.BgTableEntries[i]; - if (entry.BgIndex1 > 0) + BgTableFile bgTable = Dat.Files.First(f => f.Name == "BGTBLS").CastTo(); + tracker.Focus("Backgrounds", bgTable.BgTableEntries.Count); + for (int i = 0; i < bgTable.BgTableEntries.Count; i++) { - GraphicsFile nameGraphic = Grp.Files.First(g => g.Index == entry.BgIndex1); - string name = $"BG_{nameGraphic.Name[0..(nameGraphic.Name.LastIndexOf('_'))]}"; - string bgNameBackup = name; - for (int j = 1; Items.Select(i => i.Name).Contains(name); j++) + BgTableEntry entry = bgTable.BgTableEntries[i]; + if (entry.BgIndex1 > 0) { - name = $"{bgNameBackup}{j:D2}"; + GraphicsFile nameGraphic = Grp.Files.First(g => g.Index == entry.BgIndex1); + string name = $"BG_{nameGraphic.Name[0..(nameGraphic.Name.LastIndexOf('_'))]}"; + string bgNameBackup = name; + for (int j = 1; Items.Select(i => i.Name).Contains(name); j++) + { + name = $"{bgNameBackup}{j:D2}"; + } + Items.Add(new BackgroundItem(name, i, entry, this)); } - Items.Add(new BackgroundItem(name, i, entry, this)); + tracker.Finished++; } - tracker.Finished++; } - - if (VoiceMapIsV06OrHigher()) + catch (Exception ex) { - VoiceMap = Evt.Files.First(v => v.Name == "VOICEMAPS").CastTo(); + log.LogException($"Failed to background items", ex); + return new(LoadProjectState.FAILED); } - string[] bgmFiles = Directory.GetFiles(Path.Combine(IterativeDirectory, "rom", "data", "bgm")).OrderBy(s => s).ToArray(); - tracker.Focus("BGM Tracks", bgmFiles.Length); - for (int i = 0; i < bgmFiles.Length; i++) + try { - Items.Add(new BackgroundMusicItem(bgmFiles[i], i, this)); - tracker.Finished++; + if (VoiceMapIsV06OrHigher()) + { + VoiceMap = Evt.Files.First(v => v.Name == "VOICEMAPS").CastTo(); + } + } + catch (Exception ex) + { + log.LogException($"Failed to load voice map", ex); + return new(LoadProjectState.FAILED); } - string[] voiceFiles = Directory.GetFiles(Path.Combine(IterativeDirectory, "rom", "data", "vce")).OrderBy(s => s).ToArray(); - tracker.Focus("Voiced Lines", voiceFiles.Length); - for (int i = 0; i < voiceFiles.Length; i++) + try { - Items.Add(new VoicedLineItem(voiceFiles[i], i + 1, this)); - tracker.Finished++; + string[] bgmFiles = Directory.GetFiles(Path.Combine(IterativeDirectory, "rom", "data", "bgm")).OrderBy(s => s).ToArray(); + tracker.Focus("BGM Tracks", bgmFiles.Length); + for (int i = 0; i < bgmFiles.Length; i++) + { + Items.Add(new BackgroundMusicItem(bgmFiles[i], i, this)); + tracker.Finished++; + } + } + catch (Exception ex) + { + log.LogException($"Failed to load BGM tracks", ex); + return new(LoadProjectState.FAILED); } - tracker.Focus("Character Sprites", 1); - CharacterDataFile chrdata = Dat.Files.First(d => d.Name == "CHRDATAS").CastTo(); - Items.AddRange(chrdata.Sprites.Where(s => (int)s.Character > 0).Select(s => new CharacterSpriteItem(s, chrdata, this))); - tracker.Finished++; + try + { + string[] voiceFiles = Directory.GetFiles(Path.Combine(IterativeDirectory, "rom", "data", "vce")).OrderBy(s => s).ToArray(); + tracker.Focus("Voiced Lines", voiceFiles.Length); + for (int i = 0; i < voiceFiles.Length; i++) + { + Items.Add(new VoicedLineItem(voiceFiles[i], i + 1, this)); + tracker.Finished++; + } + } + catch (Exception ex) + { + log.LogException($"Failed to load voiced lines", ex); + return new(LoadProjectState.FAILED); + } - ChibiFile chibiFile = Dat.Files.First(d => d.Name == "CHIBIS").CastTo(); - tracker.Focus("Chibis", chibiFile.Chibis.Count); - foreach (Chibi chibi in chibiFile.Chibis) + try { - Items.Add(new ChibiItem(chibi, this)); + tracker.Focus("Character Sprites", 1); + CharacterDataFile chrdata = Dat.Files.First(d => d.Name == "CHRDATAS").CastTo(); + Items.AddRange(chrdata.Sprites.Where(s => (int)s.Character > 0).Select(s => new CharacterSpriteItem(s, chrdata, this))); tracker.Finished++; } + catch (Exception ex) + { + log.LogException($"Failed to load character sprites", ex); + return new(LoadProjectState.FAILED); + } - tracker.Focus("Dialogue Configs", 1); - Items.AddRange(Dat.Files.First(d => d.Name == "MESSINFOS").CastTo() - .MessageInfos.Where(m => (int)m.Character > 0).Select(m => new CharacterItem(m, Characters[(int)m.Character], this))); - tracker.Finished++; - - tracker.Focus("Event Files", 1); - Items.AddRange(Evt.Files - .Where(e => !new string[] { "CHESSS", "EVTTBLS", "TOPICS", "SCENARIOS", "TUTORIALS", "VOICEMAPS" }.Contains(e.Name)) - .Select(e => new ScriptItem(e, log))); - tracker.Finished++; + try + { + ChibiFile chibiFile = Dat.Files.First(d => d.Name == "CHIBIS").CastTo(); + tracker.Focus("Chibis", chibiFile.Chibis.Count); + foreach (Chibi chibi in chibiFile.Chibis) + { + Items.Add(new ChibiItem(chibi, this)); + tracker.Finished++; + } + } + catch (Exception ex) + { + log.LogException($"Failed to load chibis", ex); + return new(LoadProjectState.FAILED); + } - tracker.Focus("Maps", 1); - QMapFile qmap = Dat.Files.First(f => f.Name == "QMAPS").CastTo(); - Items.AddRange(Dat.Files - .Where(d => qmap.QMaps.Select(q => q.Name.Replace(".", "")).Contains(d.Name)) - .Select(m => new MapItem(m.CastTo(), qmap.QMaps.FindIndex(q => q.Name.Replace(".", "") == m.Name), this))); - tracker.Finished++; + try + { + tracker.Focus("Characters", 1); + Items.AddRange(Dat.Files.First(d => d.Name == "MESSINFOS").CastTo() + .MessageInfos.Where(m => (int)m.Character > 0).Select(m => new CharacterItem(m, Characters[(int)m.Character], this))); + tracker.Finished++; + } + catch (Exception ex) + { + log.LogException($"Failed to load characters", ex); + return new(LoadProjectState.FAILED); + } - PlaceFile placeFile = Dat.Files.First(f => f.Name == "PLACES").CastTo(); - tracker.Focus("Places", placeFile.PlaceGraphicIndices.Count); - for (int i = 0; i < placeFile.PlaceGraphicIndices.Count; i++) + try + { + tracker.Focus("Scripts", 1); + Items.AddRange(Evt.Files + .Where(e => !new string[] { "CHESSS", "EVTTBLS", "TOPICS", "SCENARIOS", "TUTORIALS", "VOICEMAPS" }.Contains(e.Name)) + .Select(e => new ScriptItem(e, log))); + tracker.Finished++; + } + catch (Exception ex) { - GraphicsFile placeGrp = Grp.Files.First(g => g.Index == placeFile.PlaceGraphicIndices[i]); - Items.Add(new PlaceItem(i, placeGrp, this)); + log.LogException($"Failed to load scripts", ex); + return new(LoadProjectState.FAILED); } - tracker.Finished++; - tracker.Focus("Puzzles", 1); - Items.AddRange(Dat.Files - .Where(d => d.Name.StartsWith("SLG")) - .Select(d => new PuzzleItem(d.CastTo(), this, log))); - tracker.Finished++; + try + { + tracker.Focus("Maps", 1); + QMapFile qmap = Dat.Files.First(f => f.Name == "QMAPS").CastTo(); + Items.AddRange(Dat.Files + .Where(d => qmap.QMaps.Select(q => q.Name.Replace(".", "")).Contains(d.Name)) + .Select(m => new MapItem(m.CastTo(), qmap.QMaps.FindIndex(q => q.Name.Replace(".", "") == m.Name), this))); + tracker.Finished++; + } + catch (Exception ex) + { + log.LogException($"Failed to load maps", ex); + return new(LoadProjectState.FAILED); + } - Evt.Files.First(f => f.Name == "TOPICS").InitializeTopicFile(); - TopicFile = Evt.Files.First(f => f.Name == "TOPICS"); - tracker.Focus("Topics", TopicFile.TopicStructs.Count); - foreach (TopicStruct topic in TopicFile.TopicStructs) + try { - // Main topics have shadow topics that are located at ID + 40 (this is actually how the game finds them) - // So if we're a main topic and we see another topic 40 back, we know we're one of these shadow topics and should really be - // rolled into the original main topic - if (topic.Type == TopicType.Main && Items.Any(i => i.Type == ItemDescription.ItemType.Topic && ((TopicItem)i).Topic.Id == topic.Id - 40)) - { - ((TopicItem)Items.First(i => i.Type == ItemDescription.ItemType.Topic && ((TopicItem)i).Topic.Id == topic.Id - 40)).HiddenMainTopic = topic; - } - else + PlaceFile placeFile = Dat.Files.First(f => f.Name == "PLACES").CastTo(); + tracker.Focus("Places", placeFile.PlaceGraphicIndices.Count); + for (int i = 0; i < placeFile.PlaceGraphicIndices.Count; i++) { - Items.Add(new TopicItem(topic, this)); + GraphicsFile placeGrp = Grp.Files.First(g => g.Index == placeFile.PlaceGraphicIndices[i]); + Items.Add(new PlaceItem(i, placeGrp, this)); } tracker.Finished++; } - - SystemTextureFile systemTextureFile = Dat.Files.First(f => f.Name == "SYSTEXS").CastTo(); - tracker.Focus("System Textures", - 5 + systemTextureFile.SystemTextures.Count(s => Grp.Files.Where(g => g.Name.StartsWith("XTR") || g.Name.StartsWith("SYS") && !g.Name.Contains("_SPC_") && g.Name != "SYS_CMN_B12DNX" && g.Name != "SYS_PPT_001DNX").Select(g => g.Index).Distinct().Contains(s.GrpIndex))); - Items.Add(new SystemTextureItem(systemTextureFile.SystemTextures.First(s => s.GrpIndex == Grp.Files.First(g => g.Name == "LOGO_CO_SEGDNX").Index), this, "SYSTEX_SPLASH_SEGA", 0, height: 192)); - tracker.Finished++; - Items.Add(new SystemTextureItem(systemTextureFile.SystemTextures.First(s => s.GrpIndex == Grp.Files.First(g => g.Name == "LOGO_CO_AQIDNX").Index), this, "SYSTEX_SPLASH_AQI", 0, height: 192)); - tracker.Finished++; - Items.Add(new SystemTextureItem(systemTextureFile.SystemTextures.First(s => s.GrpIndex == Grp.Files.First(g => g.Name == "LOGO_MW_ACTDNX").Index), this, "SYSTEX_SPLASH_MOBICLIP", 0, height: 192)); - tracker.Finished++; - string criLogoName = Grp.Files.Any(f => f.Name == "CREDITS") ? "SYSTEX_SPLASH_HAROOHIE" : "SYSTEX_SPLASH_CRIWARE"; - Items.Add(new SystemTextureItem(systemTextureFile.SystemTextures.First(s => s.GrpIndex == Grp.Files.First(g => g.Name == "LOGO_MW_CRIDNX").Index), this, criLogoName, 0, height: 192)); - tracker.Finished++; - if (Grp.Files.Any(f => f.Name == "CREDITS")) + catch (Exception ex) { - Items.Add(new SystemTextureItem(systemTextureFile.SystemTextures.First(s => s.GrpIndex == Grp.Files.First(g => g.Name == "CREDITS").Index), this, "SYSTEX_SPLASH_CREDITS", 0, height: 192)); + log.LogException($"Failed to load place items", ex); + return new(LoadProjectState.FAILED); } - tracker.Finished++; - foreach (SystemTexture extraSysTex in systemTextureFile.SystemTextures.Where(s => Grp.Files.Where(g => g.Name.StartsWith("XTR")).Distinct().Select(g => g.Index).Contains(s.GrpIndex))) + + try { - Items.Add(new SystemTextureItem(extraSysTex, this, $"SYSTEX_{Grp.Files.First(g => g.Index == extraSysTex.GrpIndex).Name[0..^3]}", -1)); + tracker.Focus("Puzzles", 1); + Items.AddRange(Dat.Files + .Where(d => d.Name.StartsWith("SLG")) + .Select(d => new PuzzleItem(d.CastTo(), this, log))); tracker.Finished++; } - // Exclude B12 as that's the nameplates we replace in the character items and PPT_001 as that's the puzzle phase singularity we'll be replacing in the puzzle items - // We also exclude the "special" graphics as they do not include all of them in the SYSTEX file (should be made to be edited manually) - foreach (SystemTexture sysSysTex in systemTextureFile.SystemTextures.Where(s => Grp.Files.Where(g => g.Name.StartsWith("SYS") && !g.Name.Contains("_SPC_") && g.Name != "SYS_CMN_B12DNX" && g.Name != "SYS_PPT_001DNX").Select(g => g.Index).Contains(s.GrpIndex)).DistinctBy(s => s.GrpIndex)) + catch (Exception ex) + { + log.LogException($"Failed to load puzzle items", ex); + return new(LoadProjectState.FAILED); + } + + try { - if (Grp.Files.First(g => g.Index == sysSysTex.GrpIndex).Name[0..^4].EndsWith("T6")) + Evt.Files.First(f => f.Name == "TOPICS").InitializeTopicFile(); + TopicFile = Evt.Files.First(f => f.Name == "TOPICS"); + tracker.Focus("Topics", TopicFile.TopicStructs.Count); + foreach (TopicStruct topic in TopicFile.TopicStructs) { - // special case the ep headers - Items.Add(new SystemTextureItem(sysSysTex, this, $"SYSTEX_{Grp.Files.First(g => g.Index == sysSysTex.GrpIndex).Name[0..^3]}", -1, height: 192)); + // Main topics have shadow topics that are located at ID + 40 (this is actually how the game finds them) + // So if we're a main topic and we see another topic 40 back, we know we're one of these shadow topics and should really be + // rolled into the original main topic + if (topic.Type == TopicType.Main && Items.Any(i => i.Type == ItemDescription.ItemType.Topic && ((TopicItem)i).Topic.Id == topic.Id - 40)) + { + ((TopicItem)Items.First(i => i.Type == ItemDescription.ItemType.Topic && ((TopicItem)i).Topic.Id == topic.Id - 40)).HiddenMainTopic = topic; + } + else + { + Items.Add(new TopicItem(topic, this)); + } + tracker.Finished++; } - else + } + catch (Exception ex) + { + log.LogException($"Failed to load topics", ex); + return new(LoadProjectState.FAILED); + } + + try + { + SystemTextureFile systemTextureFile = Dat.Files.First(f => f.Name == "SYSTEXS").CastTo(); + tracker.Focus("System Textures", + 5 + systemTextureFile.SystemTextures.Count(s => Grp.Files.Where(g => g.Name.StartsWith("XTR") || g.Name.StartsWith("SYS") && !g.Name.Contains("_SPC_") && g.Name != "SYS_CMN_B12DNX" && g.Name != "SYS_PPT_001DNX").Select(g => g.Index).Distinct().Contains(s.GrpIndex))); + Items.Add(new SystemTextureItem(systemTextureFile.SystemTextures.First(s => s.GrpIndex == Grp.Files.First(g => g.Name == "LOGO_CO_SEGDNX").Index), this, "SYSTEX_LOGO_SEGA", 0, height: 192)); + tracker.Finished++; + Items.Add(new SystemTextureItem(systemTextureFile.SystemTextures.First(s => s.GrpIndex == Grp.Files.First(g => g.Name == "LOGO_CO_AQIDNX").Index), this, "SYSTEX_LOGO_AQI", 0, height: 192)); + tracker.Finished++; + Items.Add(new SystemTextureItem(systemTextureFile.SystemTextures.First(s => s.GrpIndex == Grp.Files.First(g => g.Name == "LOGO_MW_ACTDNX").Index), this, "SYSTEX_LOGO_MOBICLIP", 0, height: 192)); + tracker.Finished++; + string criLogoName = Grp.Files.Any(f => f.Name == "CREDITS") ? "SYSTEX_LOGO_HAROOHIE" : "SYSTEX_LOGO_CRIWARE"; + Items.Add(new SystemTextureItem(systemTextureFile.SystemTextures.First(s => s.GrpIndex == Grp.Files.First(g => g.Name == "LOGO_MW_CRIDNX").Index), this, criLogoName, 0, height: 192)); + tracker.Finished++; + if (Grp.Files.Any(f => f.Name == "CREDITS")) { - Items.Add(new SystemTextureItem(sysSysTex, this, $"SYSTEX_{Grp.Files.First(g => g.Index == sysSysTex.GrpIndex).Name[0..^3]}", -1)); + Items.Add(new SystemTextureItem(systemTextureFile.SystemTextures.First(s => s.GrpIndex == Grp.Files.First(g => g.Name == "CREDITS").Index), this, "SYSTEX_LOGO_CREDITS", 0, height: 192)); } tracker.Finished++; + foreach (SystemTexture extraSysTex in systemTextureFile.SystemTextures.Where(s => Grp.Files.Where(g => g.Name.StartsWith("XTR")).Distinct().Select(g => g.Index).Contains(s.GrpIndex))) + { + Items.Add(new SystemTextureItem(extraSysTex, this, $"SYSTEX_{Grp.Files.First(g => g.Index == extraSysTex.GrpIndex).Name[0..^3]}", -1)); + tracker.Finished++; + } + // Exclude B12 as that's the nameplates we replace in the character items and PPT_001 as that's the puzzle phase singularity we'll be replacing in the puzzle items + // We also exclude the "special" graphics as they do not include all of them in the SYSTEX file (should be made to be edited manually) + foreach (SystemTexture sysSysTex in systemTextureFile.SystemTextures.Where(s => Grp.Files.Where(g => g.Name.StartsWith("SYS") && !g.Name.Contains("_SPC_") && g.Name != "SYS_CMN_B12DNX" && g.Name != "SYS_PPT_001DNX").Select(g => g.Index).Contains(s.GrpIndex)).DistinctBy(s => s.GrpIndex)) + { + if (Grp.Files.First(g => g.Index == sysSysTex.GrpIndex).Name[0..^4].EndsWith("T6")) + { + // special case the ep headers + Items.Add(new SystemTextureItem(sysSysTex, this, $"SYSTEX_{Grp.Files.First(g => g.Index == sysSysTex.GrpIndex).Name[0..^3]}", -1, height: 192)); + } + else + { + Items.Add(new SystemTextureItem(sysSysTex, this, $"SYSTEX_{Grp.Files.First(g => g.Index == sysSysTex.GrpIndex).Name[0..^3]}", -1)); + } + tracker.Finished++; + } + } + catch (Exception ex) + { + log.LogException($"Failed to load system textures", ex); + return new(LoadProjectState.FAILED); } - // Scenario item must be created after script and puzzle items are constructed - tracker.Focus("Scenario", 1); - EventFile scenarioFile = Evt.Files.First(f => f.Name == "SCENARIOS"); - scenarioFile.InitializeScenarioFile(); - Items.Add(new ScenarioItem(scenarioFile.Scenario, this, log)); - tracker.Finished++; - - tracker.Focus("Group Selections", scenarioFile.Scenario.Selects.Count); - for (int i = 0; i < scenarioFile.Scenario.Selects.Count; i++) + EventFile scenarioFile; + try { - Items.Add(new GroupSelectionItem(scenarioFile.Scenario.Selects[i], i, this)); + // Scenario item must be created after script and puzzle items are constructed + tracker.Focus("Scenario", 1); + scenarioFile = Evt.Files.First(f => f.Name == "SCENARIOS"); + scenarioFile.InitializeScenarioFile(); + Items.Add(new ScenarioItem(scenarioFile.Scenario, this, log)); tracker.Finished++; } + catch (Exception ex) + { + log.LogException($"Failed to load scenario", ex); + return new(LoadProjectState.FAILED); + } + + try + { + tracker.Focus("Group Selections", scenarioFile.Scenario.Selects.Count); + for (int i = 0; i < scenarioFile.Scenario.Selects.Count; i++) + { + Items.Add(new GroupSelectionItem(scenarioFile.Scenario.Selects[i], i, this)); + tracker.Finished++; + } + } + catch (Exception ex) + { + log.LogException($"Failed to load group selections", ex); + return new(LoadProjectState.FAILED); + } if (ItemNames is null) { - ItemNames = JsonSerializer.Deserialize>(File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Defaults", "DefaultNames.json"))); - foreach (ItemDescription item in Items) + try { - if (!ItemNames.ContainsKey(item.Name) && item.CanRename) + ItemNames = JsonSerializer.Deserialize>(File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Defaults", "DefaultNames.json"))); + foreach (ItemDescription item in Items) { - ItemNames.Add(item.Name, item.DisplayName); + if (!ItemNames.ContainsKey(item.Name) && item.CanRename) + { + ItemNames.Add(item.Name, item.DisplayName); + } } } + catch (Exception ex) + { + log.LogException($"Failed to load item names", ex); + return new(LoadProjectState.FAILED); + } } for (int i = 0; i < Items.Count; i++) { if (Items[i].CanRename || Items[i].Type == ItemDescription.ItemType.Place) // We don't want to manually rename places, but they do use the display name pattern { - try + if (ItemNames.ContainsKey(Items[i].Name)) { Items[i].Rename(ItemNames[Items[i].Name]); } - catch + else { ItemNames.Add(Items[i].Name, Items[i].DisplayName); } @@ -475,11 +707,11 @@ public void MigrateProject(string newRom, ILogger log, IProgressTracker tracker) string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); NdsProjectFile.Create("temp", newRom, tempDir); - IO.CopyFiles(Path.Combine(tempDir, "data"), Path.Combine(BaseDirectory, "original", "archives"), "*.bin"); - IO.CopyFiles(Path.Combine(tempDir, "data", "bgm"), Path.Combine(BaseDirectory, "original", "bgm"), "*.bin"); - IO.CopyFiles(Path.Combine(tempDir, "data", "vce"), Path.Combine(BaseDirectory, "original", "vce"), "*.bin"); - IO.CopyFiles(Path.Combine(tempDir, "overlay"), Path.Combine(BaseDirectory, "original", "overlay"), "*.bin"); - IO.CopyFiles(Path.Combine(tempDir, "data", "movie"), Path.Combine(BaseDirectory, "rom", "data", "movie"), "*.mods"); + IO.CopyFiles(Path.Combine(tempDir, "data"), Path.Combine(BaseDirectory, "original", "archives"), log, "*.bin"); + IO.CopyFiles(Path.Combine(tempDir, "data", "bgm"), Path.Combine(BaseDirectory, "original", "bgm"), log, "*.bin"); + IO.CopyFiles(Path.Combine(tempDir, "data", "vce"), Path.Combine(BaseDirectory, "original", "vce"), log, "*.bin"); + IO.CopyFiles(Path.Combine(tempDir, "overlay"), Path.Combine(BaseDirectory, "original", "overlay"), log, "*.bin"); + IO.CopyFiles(Path.Combine(tempDir, "data", "movie"), Path.Combine(BaseDirectory, "rom", "data", "movie"), log, "*.mods"); Directory.Delete(tempDir, true); } @@ -543,9 +775,16 @@ public static (Project Project, LoadProjectResult Result) OpenProject(string pro } } - public void Save() + public void Save(ILogger log) { - File.WriteAllText(Path.Combine(MainDirectory, $"{Name}.{PROJECT_FORMAT}"), JsonSerializer.Serialize(this, SERIALIZER_OPTIONS)); + try + { + File.WriteAllText(Path.Combine(MainDirectory, $"{Name}.{PROJECT_FORMAT}"), JsonSerializer.Serialize(this, SERIALIZER_OPTIONS)); + } + catch (Exception ex) + { + log.LogException("Failed to save project file! Check logs for more information.", ex); + } } public List GetSearchResults(string query, ILogger logger) @@ -553,22 +792,30 @@ public List GetSearchResults(string query, ILogger logger) return GetSearchResults(SearchQuery.Create(query), logger); } - public List GetSearchResults(SearchQuery query, ILogger logger, IProgressTracker? tracker = null) + public List GetSearchResults(SearchQuery query, ILogger log, IProgressTracker? tracker = null) { var term = query.Term.Trim(); var searchable = Items.Where(i => query.Types.Contains(i.Type)).ToList(); tracker?.Focus($"{searchable.Count} Items", searchable.Count); - return searchable.Where(item => + try + { + return searchable.Where(item => { bool hit = query.Scopes.Aggregate( false, - (current, scope) => current || ItemMatches(item, term, scope, logger) + (current, scope) => current || ItemMatches(item, term, scope, log) ); if (tracker is not null) tracker.Finished++; return hit; }) - .ToList(); + .ToList(); + } + catch (Exception ex) + { + log.LogException("Failed to get search results!", ex); + return Array.Empty().ToList(); + } } private bool ItemMatches(ItemDescription item, string term, SearchQuery.DataHolder scope, ILogger logger) diff --git a/src/SerialLoops.Lib/SerialLoops.Lib.csproj b/src/SerialLoops.Lib/SerialLoops.Lib.csproj index a2821d18..17eab832 100644 --- a/src/SerialLoops.Lib/SerialLoops.Lib.csproj +++ b/src/SerialLoops.Lib/SerialLoops.Lib.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/SerialLoops/Dialogs/AsmHacksDialog.cs b/src/SerialLoops/Dialogs/AsmHacksDialog.cs index 1a5b1af1..e75e9867 100644 --- a/src/SerialLoops/Dialogs/AsmHacksDialog.cs +++ b/src/SerialLoops/Dialogs/AsmHacksDialog.cs @@ -150,8 +150,7 @@ public AsmHacksDialog(Project project, Config config, ILogger log) } catch (Exception ex) { - log.LogError($"Failed to read ARM9 from '{arm9Path}'"); - log.LogWarning($"{ex.Message}\n\n{ex.StackTrace}"); + log.LogException($"Failed to read ARM9 from '{arm9Path}'", ex); } try @@ -163,8 +162,7 @@ public AsmHacksDialog(Project project, Config config, ILogger log) } catch (Exception ex) { - log.LogError("Failed to insert ARM9 assembly hacks"); - log.LogWarning($"{ex.Message}\n\n{ex.StackTrace}"); + log.LogException("Failed to insert ARM9 assembly hacks", ex); } try @@ -173,8 +171,7 @@ public AsmHacksDialog(Project project, Config config, ILogger log) } catch (Exception ex) { - log.LogError("Failed to write ARM9 to disk"); - log.LogWarning($"{ex.Message}\n\n{ex.StackTrace}"); + log.LogException("Failed to write ARM9 to disk", ex); } } else @@ -220,8 +217,7 @@ public AsmHacksDialog(Project project, Config config, ILogger log) } catch (Exception ex) { - log.LogError($"Failed to insert hacks into overlay {overlays[i].Name}"); - log.LogWarning($"{ex.Message}\n\n{ex.StackTrace}"); + log.LogException($"Failed to insert hacks into overlay {overlays[i].Name}", ex); } } } @@ -240,7 +236,7 @@ public AsmHacksDialog(Project project, Config config, ILogger log) catch (Exception ex) { log.LogError($"Failed saving overlay {overlay.Name} to disk"); - log.LogWarning($"{ex.Message}\n\n{ex.StackTrace}"); + log.LogException($"{ex.Message}\n\n{ex.StackTrace}", ex); } } diff --git a/src/SerialLoops/Dialogs/ProjectCreationDialog.eto.cs b/src/SerialLoops/Dialogs/ProjectCreationDialog.eto.cs index 96bac424..0c2c08f8 100644 --- a/src/SerialLoops/Dialogs/ProjectCreationDialog.eto.cs +++ b/src/SerialLoops/Dialogs/ProjectCreationDialog.eto.cs @@ -176,7 +176,7 @@ private void CreateCommand_Executed(object sender, EventArgs e) ProgressDialog _ = new(() => { ((IProgressTracker)tracker).Focus("Creating Project", 1); - IO.OpenRom(NewProject, romPath, tracker); + IO.OpenRom(NewProject, romPath, Log, tracker); tracker.Finished++; NewProject.Load(Config, Log, tracker); }, Close, tracker, "Creating Project"); diff --git a/src/SerialLoops/Editors/ScriptEditor.cs b/src/SerialLoops/Editors/ScriptEditor.cs index 352a155c..81bd254e 100644 --- a/src/SerialLoops/Editors/ScriptEditor.cs +++ b/src/SerialLoops/Editors/ScriptEditor.cs @@ -886,1138 +886,1061 @@ private StackLayout GetChoiceLayout(ChoicesSectionEntry choice, StackLayout pare private void CommandsPanel_SelectedItemChanged(object sender, EventArgs e) { - ScriptItemCommand command = ((ScriptCommandSectionEntry)((ScriptCommandSectionTreeGridView)sender).SelectedItem).Command; - _editorControls.Items.Clear(); + try + { + ScriptItemCommand command = ((ScriptCommandSectionEntry)((ScriptCommandSectionTreeGridView)sender).SelectedItem).Command; + _editorControls.Items.Clear(); - _addCommandButton.Enabled = true; - _addSectionButton.Enabled = true; - _deleteButton.Enabled = true; + _addCommandButton.Enabled = true; + _addSectionButton.Enabled = true; + _deleteButton.Enabled = true; - // if we've selected a script section header - if (command is null) - { - return; - } + // if we've selected a script section header + if (command is null) + { + return; + } - Application.Instance.Invoke(() => UpdatePreview()); + Application.Instance.Invoke(() => UpdatePreview()); - if (command.Parameters.Count == 0) - { - return; - } + if (command.Parameters.Count == 0) + { + return; + } - int cols = 1; + int cols = 1; - // We're going to embed table layouts inside a table layout so we can have colspan - TableLayout controlsTable = new() - { - Spacing = new Size(5, 5) - }; - controlsTable.Rows.Add(new(new TableLayout { Spacing = new Size(5, 5) })); - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows.Add(new()); + // We're going to embed table layouts inside a table layout so we can have colspan + TableLayout controlsTable = new() + { + Spacing = new Size(5, 5) + }; + controlsTable.Rows.Add(new(new TableLayout { Spacing = new Size(5, 5) })); + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows.Add(new()); - int currentRow = 0, currentCol = 0, currentShort = 0; - for (int i = 0; i < command.Parameters.Count; i++) - { - ScriptParameter parameter = command.Parameters[i]; - switch (parameter.Type) + int currentRow = 0, currentCol = 0, currentShort = 0; + for (int i = 0; i < command.Parameters.Count; i++) { - case ScriptParameter.ParameterType.BG: - BgScriptParameter bgParam = (BgScriptParameter)parameter; - CommandGraphicSelectionButton bgSelectionButton = new(bgParam.Background is not null ? bgParam.Background - : NonePreviewableGraphic.BACKGROUND, _tabs, _log) - { - Command = command, - ParameterIndex = i, - Project = _project, - }; + ScriptParameter parameter = command.Parameters[i]; + switch (parameter.Type) + { + case ScriptParameter.ParameterType.BG: + BgScriptParameter bgParam = (BgScriptParameter)parameter; + CommandGraphicSelectionButton bgSelectionButton = new(bgParam.Background is not null ? bgParam.Background + : NonePreviewableGraphic.BACKGROUND, _tabs, _log) + { + Command = command, + ParameterIndex = i, + Project = _project, + }; - // BGDISPTEMP is able to display a lot more kinds of backgrounds properly than the other BG commands - // Hence, this switch to make sure you don't accidentally crash the game - if (command.Verb == CommandVerb.BG_FADE) - { - bgSelectionButton.Items.Add(NonePreviewableGraphic.BACKGROUND); - switch (i) + // BGDISPTEMP is able to display a lot more kinds of backgrounds properly than the other BG commands + // Hence, this switch to make sure you don't accidentally crash the game + if (command.Verb == CommandVerb.BG_FADE) { - case 0: - bgSelectionButton.Items.AddRange(_project.Items.Where(i => i.Type == ItemDescription.ItemType.Background && - (((BackgroundItem)i).BackgroundType == BgType.TEX_BG)) - .Select(b => b as IPreviewableGraphic)); - break; - case 1: - bgSelectionButton.Items.AddRange(_project.Items.Where(i => i.Type == ItemDescription.ItemType.Background && - ((BackgroundItem)i).BackgroundType != BgType.KINETIC_SCREEN && ((BackgroundItem)i).BackgroundType != BgType.TEX_BG) - .Select(b => b as IPreviewableGraphic)); - break; + bgSelectionButton.Items.Add(NonePreviewableGraphic.BACKGROUND); + switch (i) + { + case 0: + bgSelectionButton.Items.AddRange(_project.Items.Where(i => i.Type == ItemDescription.ItemType.Background && + (((BackgroundItem)i).BackgroundType == BgType.TEX_BG)) + .Select(b => b as IPreviewableGraphic)); + break; + case 1: + bgSelectionButton.Items.AddRange(_project.Items.Where(i => i.Type == ItemDescription.ItemType.Background && + ((BackgroundItem)i).BackgroundType != BgType.KINETIC_SCREEN && ((BackgroundItem)i).BackgroundType != BgType.TEX_BG) + .Select(b => b as IPreviewableGraphic)); + break; + } } - } - else - { - switch (command.Verb) + else { - case CommandVerb.BG_DISP: - case CommandVerb.BG_DISP2: - bgSelectionButton.Items.AddRange(_project.Items.Where(i => i.Type == ItemDescription.ItemType.Background && - (((BackgroundItem)i).BackgroundType == BgType.TEX_BG)) - .Select(b => b as IPreviewableGraphic)); - break; + switch (command.Verb) + { + case CommandVerb.BG_DISP: + case CommandVerb.BG_DISP2: + bgSelectionButton.Items.AddRange(_project.Items.Where(i => i.Type == ItemDescription.ItemType.Background && + (((BackgroundItem)i).BackgroundType == BgType.TEX_BG)) + .Select(b => b as IPreviewableGraphic)); + break; - case CommandVerb.BG_DISPCG: - bgSelectionButton.Items.AddRange(_project.Items.Where(i => i.Type == ItemDescription.ItemType.Background && - ((BackgroundItem)i).BackgroundType != BgType.KINETIC_SCREEN && ((BackgroundItem)i).BackgroundType != BgType.TEX_BG) - .Select(b => b as IPreviewableGraphic)); - break; + case CommandVerb.BG_DISPCG: + bgSelectionButton.Items.AddRange(_project.Items.Where(i => i.Type == ItemDescription.ItemType.Background && + ((BackgroundItem)i).BackgroundType != BgType.KINETIC_SCREEN && ((BackgroundItem)i).BackgroundType != BgType.TEX_BG) + .Select(b => b as IPreviewableGraphic)); + break; - case CommandVerb.KBG_DISP: - bgSelectionButton.Items.AddRange(_project.Items.Where(i => i.Type == ItemDescription.ItemType.Background && - ((BackgroundItem)i).BackgroundType == BgType.KINETIC_SCREEN).Select(b => b as IPreviewableGraphic)); - break; + case CommandVerb.KBG_DISP: + bgSelectionButton.Items.AddRange(_project.Items.Where(i => i.Type == ItemDescription.ItemType.Background && + ((BackgroundItem)i).BackgroundType == BgType.KINETIC_SCREEN).Select(b => b as IPreviewableGraphic)); + break; + } } - } - bgSelectionButton.SelectedChanged.Executed += (obj, args) => BgSelectionButton_SelectionMade(bgSelectionButton, args); + bgSelectionButton.SelectedChanged.Executed += (obj, args) => BgSelectionButton_SelectionMade(bgSelectionButton, args); - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, bgSelectionButton)); - break; + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, bgSelectionButton)); + break; - case ScriptParameter.ParameterType.BG_SCROLL_DIRECTION: - ScriptCommandDropDown bgScrollDropDown = new() { Command = command, ParameterIndex = i }; - bgScrollDropDown.Items.AddRange(Enum.GetNames().Select(i => new ListItem { Text = i, Key = i })); - bgScrollDropDown.SelectedKey = ((BgScrollDirectionScriptParameter)parameter).ScrollDirection.ToString(); - bgScrollDropDown.SelectedKeyChanged += BgScrollDropDown_SelectedKeyChanged; + case ScriptParameter.ParameterType.BG_SCROLL_DIRECTION: + ScriptCommandDropDown bgScrollDropDown = new() { Command = command, ParameterIndex = i }; + bgScrollDropDown.Items.AddRange(Enum.GetNames().Select(i => new ListItem { Text = i, Key = i })); + bgScrollDropDown.SelectedKey = ((BgScrollDirectionScriptParameter)parameter).ScrollDirection.ToString(); + bgScrollDropDown.SelectedKeyChanged += BgScrollDropDown_SelectedKeyChanged; - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, bgScrollDropDown)); - break; + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, bgScrollDropDown)); + break; - case ScriptParameter.ParameterType.BGM: - BgmScriptParameter bgmParam = (BgmScriptParameter)parameter; - StackLayout bgmLink = ControlGenerator.GetFileLink(bgmParam.Bgm, _tabs, _log); + case ScriptParameter.ParameterType.BGM: + BgmScriptParameter bgmParam = (BgmScriptParameter)parameter; + StackLayout bgmLink = ControlGenerator.GetFileLink(bgmParam.Bgm, _tabs, _log); - ScriptCommandDropDown bgmDropDown = new() { Command = command, ParameterIndex = i, Link = (ClearableLinkButton)bgmLink.Items[1].Control }; - bgmDropDown.Items.AddRange(_project.Items.Where(i => i.Type == ItemDescription.ItemType.BGM).Select(i => new ListItem { Text = i.DisplayName, Key = i.DisplayName })); - bgmDropDown.SelectedKey = bgmParam.Bgm.DisplayName; - bgmDropDown.SelectedKeyChanged += BgmDropDown_SelectedKeyChanged; + ScriptCommandDropDown bgmDropDown = new() { Command = command, ParameterIndex = i, Link = (ClearableLinkButton)bgmLink.Items[1].Control }; + bgmDropDown.Items.AddRange(_project.Items.Where(i => i.Type == ItemDescription.ItemType.BGM).Select(i => new ListItem { Text = i.DisplayName, Key = i.DisplayName })); + bgmDropDown.SelectedKey = bgmParam.Bgm.DisplayName; + bgmDropDown.SelectedKeyChanged += BgmDropDown_SelectedKeyChanged; - StackLayout bgmLayout = new() - { - Orientation = Orientation.Horizontal, - VerticalContentAlignment = VerticalAlignment.Center, - Items = + StackLayout bgmLayout = new() + { + Orientation = Orientation.Horizontal, + VerticalContentAlignment = VerticalAlignment.Center, + Items = { ControlGenerator.GetControlWithLabel(parameter.Name, bgmDropDown), bgmLink, } - }; + }; - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add(bgmLayout); - break; - - case ScriptParameter.ParameterType.BGM_MODE: - ScriptCommandDropDown bgmModeDropDown = new() { Command = command, ParameterIndex = i }; - bgmModeDropDown.Items.AddRange(Enum.GetNames().Select(i => new ListItem { Text = i, Key = i })); - bgmModeDropDown.SelectedKey = ((BgmModeScriptParameter)parameter).Mode.ToString(); - bgmModeDropDown.SelectedKeyChanged += BgmModeDropDown_SelectedKeyChanged; - - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, bgmModeDropDown)); - break; - - case ScriptParameter.ParameterType.BOOL: - ScriptCommandCheckBox boolParameterCheckbox = new() { Command = command, ParameterIndex = i, Checked = ((BoolScriptParameter)parameter).Value }; - boolParameterCheckbox.CheckedChanged += BoolParameterCheckbox_CheckedChanged; - - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, boolParameterCheckbox)); - break; - - case ScriptParameter.ParameterType.CHARACTER: - ScriptCommandDropDown dialoguePropertyDropDown = new() { Command = command, ParameterIndex = i }; - dialoguePropertyDropDown.Items.AddRange(_project.Items.Where(i => i.Type == ItemDescription.ItemType.Character) - .Select(c => new ListItem { Key = c.DisplayName, Text = c.DisplayName[4..] })); - dialoguePropertyDropDown.SelectedKey = ((DialoguePropertyScriptParameter)parameter).Character.Name; - dialoguePropertyDropDown.SelectedKeyChanged += DialoguePropertyDropDown_SelectedKeyChanged; - _currentSpeakerDropDown.OtherDropDowns.Add(dialoguePropertyDropDown); - - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, dialoguePropertyDropDown)); - break; - - case ScriptParameter.ParameterType.CHESS_FILE: - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, - new TextBox { Text = ((ChessFileScriptParameter)parameter).ChessFileIndex.ToString() })); - break; - - case ScriptParameter.ParameterType.CHESS_PIECE: - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, - new TextBox { Text = ((ChessPieceScriptParameter)parameter).ChessPiece.ToString() })); - break; - - case ScriptParameter.ParameterType.CHESS_SPACE: - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, - new TextBox { Text = ((ChessSpaceScriptParameter)parameter).SpaceIndex.ToString() })); - break; - - case ScriptParameter.ParameterType.CHIBI: - ScriptCommandDropDown chibiDropDown = new() { Command = command, ParameterIndex = i }; - chibiDropDown.Items.AddRange(_project.Items.Where(i => i.Type == ItemDescription.ItemType.Chibi).Select(i => new ListItem { Text = i.Name, Key = i.Name })); - chibiDropDown.SelectedKey = ((ChibiScriptParameter)parameter).Chibi.Name; - chibiDropDown.SelectedKeyChanged += ChibiDropDown_SelectedKeyChanged; - - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, chibiDropDown)); - break; - - case ScriptParameter.ParameterType.CHIBI_EMOTE: - ScriptCommandDropDown chibiEmoteDropDown = new() { Command = command, ParameterIndex = i }; - chibiEmoteDropDown.Items.AddRange(Enum.GetNames().Select(i => new ListItem { Text = i, Key = i })); - chibiEmoteDropDown.SelectedKey = ((ChibiEmoteScriptParameter)parameter).Emote.ToString(); - chibiEmoteDropDown.SelectedKeyChanged += ChibiEmoteDropDown_SelectedKeyChanged; - - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, chibiEmoteDropDown)); - break; - - case ScriptParameter.ParameterType.CHIBI_ENTER_EXIT: - ScriptCommandDropDown chibiEnterExitDropDown = new() { Command = command, ParameterIndex = i }; - chibiEnterExitDropDown.Items.AddRange(Enum.GetNames().Select(i => new ListItem { Text = i, Key = i })); - chibiEnterExitDropDown.SelectedKey = ((ChibiEnterExitScriptParameter)parameter).Mode.ToString(); - chibiEnterExitDropDown.SelectedKeyChanged += ChibiEnterExitDropDown_SelectedKeyChanged; - - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, chibiEnterExitDropDown)); - break; - - case ScriptParameter.ParameterType.COLOR: - ScriptCommandColorPicker colorPicker = new() - { - AllowAlpha = false, - Value = ((ColorScriptParameter)parameter).Color.ToEtoDrawingColor(), - Command = command, - ParameterIndex = i, - }; - colorPicker.ValueChanged += ColorPicker_ValueChanged; - currentShort += 2; - - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, colorPicker)); - break; - - case ScriptParameter.ParameterType.CONDITIONAL: - ScriptCommandTextBox conditionalBox = new() { Text = ((ConditionalScriptParameter)parameter).Conditional, Command = command, ParameterIndex = i }; - conditionalBox.TextChanged += ConditionalBox_TextChanged; - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, - conditionalBox)); - break; - - case ScriptParameter.ParameterType.COLOR_MONOCHROME: - ScriptCommandDropDown colorMonochromeDropDown = new() { Command = command, ParameterIndex = i, CurrentShort = currentShort }; - colorMonochromeDropDown.Items.AddRange(Enum.GetNames().Select(i => new ListItem { Text = i, Key = i })); - colorMonochromeDropDown.SelectedKey = ((ColorMonochromeScriptParameter)parameter).ColorType.ToString(); - colorMonochromeDropDown.SelectedKeyChanged += ColorMonochromeDropDown_SelectedKeyChanged; - - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, colorMonochromeDropDown)); - break; - - case ScriptParameter.ParameterType.DIALOGUE: - DialogueScriptParameter dialogueParam = (DialogueScriptParameter)parameter; - ScriptCommandDropDown speakerDropDown = new() { Command = command, ParameterIndex = i, OtherDropDowns = new() }; - speakerDropDown.Items.AddRange(_project.Items.Where(i => i.Type == ItemDescription.ItemType.Character).Select(c => new ListItem { Key = c.DisplayName, Text = c.DisplayName[4..] })); - try - { - speakerDropDown.SelectedKey = _project.Items.First(i => i.Type == ItemDescription.ItemType.Character && i.DisplayName == $"CHR_{_project.Characters[(int)dialogueParam.Line.Speaker].Name}").DisplayName; - } - catch (InvalidOperationException) - { - _log.LogError("Failed to find character item -- have you saved all of your changes to character names?"); - return; - } + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add(bgmLayout); + break; - speakerDropDown.SelectedKeyChanged += SpeakerDropDown_SelectedKeyChanged; - if (currentCol > 0) - { - controlsTable.Rows.Add(new(new TableLayout { Spacing = new Size(5, 5) })); - currentRow++; - currentCol = 0; - } - _currentSpeakerDropDown = speakerDropDown; + case ScriptParameter.ParameterType.BGM_MODE: + ScriptCommandDropDown bgmModeDropDown = new() { Command = command, ParameterIndex = i }; + bgmModeDropDown.Items.AddRange(Enum.GetNames().Select(i => new ListItem { Text = i, Key = i })); + bgmModeDropDown.SelectedKey = ((BgmModeScriptParameter)parameter).Mode.ToString(); + bgmModeDropDown.SelectedKeyChanged += BgmModeDropDown_SelectedKeyChanged; - ScriptCommandTextArea dialogueTextArea = new() - { - Text = dialogueParam.Line.Text.GetSubstitutedString(_project), - AcceptsReturn = true, - Command = command, - ParameterIndex = i, - }; - dialogueTextArea.TextChanged += DialogueTextArea_TextChanged; + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, bgmModeDropDown)); + break; + + case ScriptParameter.ParameterType.BOOL: + ScriptCommandCheckBox boolParameterCheckbox = new() { Command = command, ParameterIndex = i, Checked = ((BoolScriptParameter)parameter).Value }; + boolParameterCheckbox.CheckedChanged += BoolParameterCheckbox_CheckedChanged; + + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, boolParameterCheckbox)); + break; + + case ScriptParameter.ParameterType.CHARACTER: + ScriptCommandDropDown dialoguePropertyDropDown = new() { Command = command, ParameterIndex = i }; + dialoguePropertyDropDown.Items.AddRange(_project.Items.Where(i => i.Type == ItemDescription.ItemType.Character) + .Select(c => new ListItem { Key = c.DisplayName, Text = c.DisplayName[4..] })); + dialoguePropertyDropDown.SelectedKey = ((DialoguePropertyScriptParameter)parameter).Character.Name; + dialoguePropertyDropDown.SelectedKeyChanged += DialoguePropertyDropDown_SelectedKeyChanged; + _currentSpeakerDropDown.OtherDropDowns.Add(dialoguePropertyDropDown); + + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, dialoguePropertyDropDown)); + break; + + case ScriptParameter.ParameterType.CHESS_FILE: + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, + new TextBox { Text = ((ChessFileScriptParameter)parameter).ChessFileIndex.ToString() })); + break; + + case ScriptParameter.ParameterType.CHESS_PIECE: + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, + new TextBox { Text = ((ChessPieceScriptParameter)parameter).ChessPiece.ToString() })); + break; + + case ScriptParameter.ParameterType.CHESS_SPACE: + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, + new TextBox { Text = ((ChessSpaceScriptParameter)parameter).SpaceIndex.ToString() })); + break; + + case ScriptParameter.ParameterType.CHIBI: + ScriptCommandDropDown chibiDropDown = new() { Command = command, ParameterIndex = i }; + chibiDropDown.Items.AddRange(_project.Items.Where(i => i.Type == ItemDescription.ItemType.Chibi).Select(i => new ListItem { Text = i.Name, Key = i.Name })); + chibiDropDown.SelectedKey = ((ChibiScriptParameter)parameter).Chibi.Name; + chibiDropDown.SelectedKeyChanged += ChibiDropDown_SelectedKeyChanged; + + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, chibiDropDown)); + break; + + case ScriptParameter.ParameterType.CHIBI_EMOTE: + ScriptCommandDropDown chibiEmoteDropDown = new() { Command = command, ParameterIndex = i }; + chibiEmoteDropDown.Items.AddRange(Enum.GetNames().Select(i => new ListItem { Text = i, Key = i })); + chibiEmoteDropDown.SelectedKey = ((ChibiEmoteScriptParameter)parameter).Emote.ToString(); + chibiEmoteDropDown.SelectedKeyChanged += ChibiEmoteDropDown_SelectedKeyChanged; + + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, chibiEmoteDropDown)); + break; + + case ScriptParameter.ParameterType.CHIBI_ENTER_EXIT: + ScriptCommandDropDown chibiEnterExitDropDown = new() { Command = command, ParameterIndex = i }; + chibiEnterExitDropDown.Items.AddRange(Enum.GetNames().Select(i => new ListItem { Text = i, Key = i })); + chibiEnterExitDropDown.SelectedKey = ((ChibiEnterExitScriptParameter)parameter).Mode.ToString(); + chibiEnterExitDropDown.SelectedKeyChanged += ChibiEnterExitDropDown_SelectedKeyChanged; - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabelTable(parameter.Name, - new StackLayout + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, chibiEnterExitDropDown)); + break; + + case ScriptParameter.ParameterType.COLOR: + ScriptCommandColorPicker colorPicker = new() { - Orientation = Orientation.Horizontal, - Items = + AllowAlpha = false, + Value = ((ColorScriptParameter)parameter).Color.ToEtoDrawingColor(), + Command = command, + ParameterIndex = i, + }; + colorPicker.ValueChanged += ColorPicker_ValueChanged; + currentShort += 2; + + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, colorPicker)); + break; + + case ScriptParameter.ParameterType.CONDITIONAL: + ScriptCommandTextBox conditionalBox = new() { Text = ((ConditionalScriptParameter)parameter).Conditional, Command = command, ParameterIndex = i }; + conditionalBox.TextChanged += ConditionalBox_TextChanged; + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, + conditionalBox)); + break; + + case ScriptParameter.ParameterType.COLOR_MONOCHROME: + ScriptCommandDropDown colorMonochromeDropDown = new() { Command = command, ParameterIndex = i, CurrentShort = currentShort }; + colorMonochromeDropDown.Items.AddRange(Enum.GetNames().Select(i => new ListItem { Text = i, Key = i })); + colorMonochromeDropDown.SelectedKey = ((ColorMonochromeScriptParameter)parameter).ColorType.ToString(); + colorMonochromeDropDown.SelectedKeyChanged += ColorMonochromeDropDown_SelectedKeyChanged; + + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, colorMonochromeDropDown)); + break; + + case ScriptParameter.ParameterType.DIALOGUE: + DialogueScriptParameter dialogueParam = (DialogueScriptParameter)parameter; + ScriptCommandDropDown speakerDropDown = new() { Command = command, ParameterIndex = i, OtherDropDowns = new() }; + speakerDropDown.Items.AddRange(_project.Items.Where(i => i.Type == ItemDescription.ItemType.Character).Select(c => new ListItem { Key = c.DisplayName, Text = c.DisplayName[4..] })); + try + { + speakerDropDown.SelectedKey = _project.Items.First(i => i.Type == ItemDescription.ItemType.Character && i.DisplayName == $"CHR_{_project.Characters[(int)dialogueParam.Line.Speaker].Name}").DisplayName; + } + catch (InvalidOperationException) + { + _log.LogError("Failed to find character item -- have you saved all of your changes to character names?"); + return; + } + + speakerDropDown.SelectedKeyChanged += SpeakerDropDown_SelectedKeyChanged; + if (currentCol > 0) + { + controlsTable.Rows.Add(new(new TableLayout { Spacing = new Size(5, 5) })); + currentRow++; + currentCol = 0; + } + _currentSpeakerDropDown = speakerDropDown; + + ScriptCommandTextArea dialogueTextArea = new() + { + Text = dialogueParam.Line.Text.GetSubstitutedString(_project), + AcceptsReturn = true, + Command = command, + ParameterIndex = i, + }; + dialogueTextArea.TextChanged += DialogueTextArea_TextChanged; + + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabelTable(parameter.Name, + new StackLayout { + Orientation = Orientation.Horizontal, + Items = + { speakerDropDown, new StackLayoutItem(dialogueTextArea, expand: true), - }, - })); - controlsTable.Rows.Add(new(new TableLayout { Spacing = new Size(5, 5) })); - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows.Add(new()); - currentRow++; - currentCol = 0; - break; - - case ScriptParameter.ParameterType.EPISODE_HEADER: - ScriptCommandDropDown epHeaderDropDown = new() { Command = command, ParameterIndex = i }; - epHeaderDropDown.Items.AddRange(Enum.GetNames().Select(e => new ListItem { Key = e, Text = e })); - epHeaderDropDown.SelectedKey = ((EpisodeHeaderScriptParameter)parameter).EpisodeHeaderIndex.ToString(); - epHeaderDropDown.SelectedKeyChanged += EpHeaderDropDown_SelectedKeyChanged; - - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, - epHeaderDropDown)); - break; - - case ScriptParameter.ParameterType.FLAG: - ScriptCommandTextBox flagTextBox = new() { Command = command, ParameterIndex = i, Text = ((FlagScriptParameter)parameter).FlagName }; - flagTextBox.TextChanged += FlagTextBox_TextChanged; - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, flagTextBox)); - break; - - case ScriptParameter.ParameterType.FRIENDSHIP_LEVEL: - ScriptCommandDropDown friendshipLevelDropDown = new() { Command = command, ParameterIndex = i }; - friendshipLevelDropDown.Items.AddRange(Enum.GetNames().Select(f => new ListItem { Key = f, Text = f })); - friendshipLevelDropDown.SelectedKey = ((FriendshipLevelScriptParameter)parameter).Character.ToString(); - friendshipLevelDropDown.SelectedIndexChanged += FriendshipLevelDropDown_SelectedIndexChanged; - - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, friendshipLevelDropDown)); - break; - - case ScriptParameter.ParameterType.ITEM: - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, - new TextBox { Text = ((ItemScriptParameter)parameter).ItemIndex.ToString() })); - break; - - case ScriptParameter.ParameterType.MAP: - MapScriptParameter mapParam = (MapScriptParameter)parameter; - ScriptCommandDropDown mapDropDown = new() { Command = command, ParameterIndex = i }; - mapDropDown.Items.Add(new ListItem { Text = "NONE", Key = "NONE" }); - mapDropDown.Items.AddRange(_project.Items.Where(i => i.Type == ItemDescription.ItemType.Map).Select(i => new ListItem { Text = i.Name, Key = i.Name })); - mapDropDown.SelectedKey = mapParam.Map?.Name ?? "NONE"; - mapDropDown.SelectedKeyChanged += MapDropDown_SelectedKeyChanged; - - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, mapDropDown)); - break; - - case ScriptParameter.ParameterType.OPTION: - OptionScriptParameter optionParam = (OptionScriptParameter)parameter; - ScriptCommandDropDown optionDropDown = new() { Command = command, ParameterIndex = i }; - optionDropDown.Items.Add(new ListItem { Text = "NONE", Key = "0" }); - optionDropDown.Items.AddRange(_script.Event.ChoicesSection.Objects.Skip(1).SkipLast(1).Select(c => new ListItem { Text = c.Text.GetSubstitutedString(_project), Key = c.Id.ToString() })); - optionDropDown.SelectedKey = optionParam.Option.Id.ToString(); - optionDropDown.SelectedKeyChanged += OptionDropDown_SelectedKeyChanged; - - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, optionDropDown)); - break; - - case ScriptParameter.ParameterType.PALETTE_EFFECT: - ScriptCommandDropDown paletteEffectDropDown = new() { Command = command, ParameterIndex = i }; - paletteEffectDropDown.Items.AddRange(Enum.GetNames().Select(t => new ListItem { Text = t, Key = t })); - paletteEffectDropDown.SelectedKey = ((PaletteEffectScriptParameter)parameter).Effect.ToString(); - paletteEffectDropDown.SelectedKeyChanged += PaletteEffectDropDown_SelectedKeyChanged; - - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, paletteEffectDropDown)); - break; - - case ScriptParameter.ParameterType.PLACE: - PlaceScriptParameter placeParam = (PlaceScriptParameter)parameter; - CommandGraphicSelectionButton placeSelectionButton = new(placeParam.Place is not null ? placeParam.Place - : NonePreviewableGraphic.PLACE, _tabs, _log) - { - Command = command, - ParameterIndex = i, - Project = _project, - }; - placeSelectionButton.Items.Add(NonePreviewableGraphic.PLACE); - placeSelectionButton.Items.AddRange(_project.Items.Where(i => i.Type == ItemDescription.ItemType.Place) - .Select(p => p as IPreviewableGraphic)); - placeSelectionButton.SelectedChanged.Executed += (obj, args) => PlaceSelectionButtonSelectedChanged_Executed(placeSelectionButton, args); + }, + })); + controlsTable.Rows.Add(new(new TableLayout { Spacing = new Size(5, 5) })); + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows.Add(new()); + currentRow++; + currentCol = 0; + break; - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, placeSelectionButton)); - break; + case ScriptParameter.ParameterType.EPISODE_HEADER: + ScriptCommandDropDown epHeaderDropDown = new() { Command = command, ParameterIndex = i }; + epHeaderDropDown.Items.AddRange(Enum.GetNames().Select(e => new ListItem { Key = e, Text = e })); + epHeaderDropDown.SelectedKey = ((EpisodeHeaderScriptParameter)parameter).EpisodeHeaderIndex.ToString(); + epHeaderDropDown.SelectedKeyChanged += EpHeaderDropDown_SelectedKeyChanged; - case ScriptParameter.ParameterType.SCREEN: - ScriptCommandScreenSelector screenSelector = new(_log, ((ScreenScriptParameter)parameter).Screen, true) - { - Command = command, - ParameterIndex = i, - CurrentShort = currentShort, - }; - screenSelector.ScreenChanged += ScreenSelector_ScreenChanged; + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, + epHeaderDropDown)); + break; - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, screenSelector)); - break; + case ScriptParameter.ParameterType.FLAG: + ScriptCommandTextBox flagTextBox = new() { Command = command, ParameterIndex = i, Text = ((FlagScriptParameter)parameter).FlagName }; + flagTextBox.TextChanged += FlagTextBox_TextChanged; + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, flagTextBox)); + break; - case ScriptParameter.ParameterType.SCRIPT_SECTION: - ScriptCommandDropDown scriptSectionDropDown = new() { Command = command, ParameterIndex = i, CurrentShort = i }; - if (command.Verb == CommandVerb.VGOTO) - { - scriptSectionDropDown.CurrentShort = 2; - } - scriptSectionDropDown.Items.AddRange(_script.Event.ScriptSections.Where(s => (_script.Event.LabelsSection.Objects.FirstOrDefault(l => l.Name.Replace("/", "") == s.Name)?.Id ?? 0) != 0).Select(s => new ListItem { Text = s.Name, Key = s.Name })); - scriptSectionDropDown.SelectedKey = ((ScriptSectionScriptParameter)parameter)?.Section?.Name ?? "NONE"; - scriptSectionDropDown.SelectedKeyChanged += ScriptSectionDropDown_SelectedKeyChanged; - - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, scriptSectionDropDown)); - break; - - case ScriptParameter.ParameterType.SFX: - ScriptCommandNumericStepper sfxNumericStepper = new() { Command = command, ParameterIndex = i, Value = ((SfxScriptParameter)parameter).SfxIndex }; - sfxNumericStepper.ValueChanged += SfxNumericStepper_ValueChanged; - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, sfxNumericStepper)); - break; - - case ScriptParameter.ParameterType.SFX_MODE: - ScriptCommandDropDown sfxModeDropDown = new() { Command = command, ParameterIndex = i }; - sfxModeDropDown.Items.AddRange(Enum.GetNames().Select(t => new ListItem { Text = t, Key = t })); - sfxModeDropDown.SelectedKey = ((SfxModeScriptParameter)parameter).Mode.ToString(); - sfxModeDropDown.SelectedKeyChanged += SfxModeDropDown_SelectedKeyChanged; - - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, sfxModeDropDown)); - break; - - case ScriptParameter.ParameterType.SHORT: - ScriptCommandNumericStepper shortNumericStepper = new() - { - Command = command, - ParameterIndex = i, - MinValue = short.MinValue, - MaxValue = short.MaxValue, - DecimalPlaces = 0, - Value = ((ShortScriptParameter)parameter).Value - }; - if (command.Verb == CommandVerb.SND_PLAY && parameter.Name == "Crossfade Time (Frames)") - { - shortNumericStepper.SecondIndex = 4; - } - if (command.Verb == CommandVerb.HARUHI_METER) - { - shortNumericStepper.ParameterIndex++; - } - shortNumericStepper.ValueChanged += ShortNumericStepper_ValueChanged; + case ScriptParameter.ParameterType.FRIENDSHIP_LEVEL: + ScriptCommandDropDown friendshipLevelDropDown = new() { Command = command, ParameterIndex = i }; + friendshipLevelDropDown.Items.AddRange(Enum.GetNames().Select(f => new ListItem { Key = f, Text = f })); + friendshipLevelDropDown.SelectedKey = ((FriendshipLevelScriptParameter)parameter).Character.ToString(); + friendshipLevelDropDown.SelectedIndexChanged += FriendshipLevelDropDown_SelectedIndexChanged; + + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, friendshipLevelDropDown)); + break; - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, shortNumericStepper)); - break; + case ScriptParameter.ParameterType.ITEM: + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, + new TextBox { Text = ((ItemScriptParameter)parameter).ItemIndex.ToString() })); + break; - case ScriptParameter.ParameterType.SPRITE: - SpriteScriptParameter spriteParam = (SpriteScriptParameter)parameter; - CommandGraphicSelectionButton spriteSelectionButton = new(spriteParam.Sprite is not null ? spriteParam.Sprite - : NonePreviewableGraphic.CHARACTER_SPRITE, _tabs, _log) - { - Command = command, - ParameterIndex = i, - Project = _project, - }; - spriteSelectionButton.Items.Add(NonePreviewableGraphic.CHARACTER_SPRITE); - spriteSelectionButton.Items.AddRange(_project.Items.Where(i => i.Type == ItemDescription.ItemType.Character_Sprite).Select(s => (IPreviewableGraphic)s)); - spriteSelectionButton.SelectedChanged.Executed += (obj, args) => SpriteSelectionButton_SelectionMade(spriteSelectionButton, args); - - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, spriteSelectionButton)); - break; - - case ScriptParameter.ParameterType.SPRITE_ENTRANCE: - ScriptCommandDropDown spriteEntranceDropDown = new() { Command = command, ParameterIndex = i }; - spriteEntranceDropDown.Items.AddRange(Enum.GetNames().Select(t => new ListItem { Text = t, Key = t })); - spriteEntranceDropDown.SelectedKey = ((SpriteEntranceScriptParameter)parameter).EntranceTransition.ToString(); - spriteEntranceDropDown.SelectedKeyChanged += SpriteEntranceDropDown_SelectedKeyChanged; - - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, spriteEntranceDropDown)); - break; - - case ScriptParameter.ParameterType.SPRITE_EXIT: - ScriptCommandDropDown spriteExitDropDown = new() { Command = command, ParameterIndex = i }; - spriteExitDropDown.Items.AddRange(Enum.GetNames().Select(t => new ListItem { Text = t, Key = t })); - spriteExitDropDown.SelectedKey = ((SpriteExitScriptParameter)parameter).ExitTransition.ToString(); - spriteExitDropDown.SelectedKeyChanged += SpriteExitDropDown_SelectedKeyChanged; - - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, spriteExitDropDown)); - break; - - case ScriptParameter.ParameterType.SPRITE_SHAKE: - ScriptCommandDropDown spriteShakeDropDown = new() { Command = command, ParameterIndex = i }; - spriteShakeDropDown.Items.AddRange(Enum.GetNames().Select(t => new ListItem { Text = t, Key = t })); - spriteShakeDropDown.SelectedKey = ((SpriteShakeScriptParameter)parameter).ShakeEffect.ToString(); - spriteShakeDropDown.SelectedKeyChanged += SpriteShakeDropDown_SelectedKeyChanged; - - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, spriteShakeDropDown)); - break; - - case ScriptParameter.ParameterType.TEXT_ENTRANCE_EFFECT: - ScriptCommandDropDown textEntranceEffectDropDown = new() { Command = command, ParameterIndex = i }; - textEntranceEffectDropDown.Items.AddRange(Enum.GetNames().Select(t => new ListItem { Text = t, Key = t })); - textEntranceEffectDropDown.SelectedKey = ((TextEntranceEffectScriptParameter)parameter).EntranceEffect.ToString(); - textEntranceEffectDropDown.SelectedKeyChanged += TextEntranceEffectDropDown_SelectedKeyChanged; - - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, textEntranceEffectDropDown)); - break; - - case ScriptParameter.ParameterType.TOPIC: - string topicName = _project.Items.FirstOrDefault(i => i.Type == ItemDescription.ItemType.Topic && - ((TopicItem)i).Topic.Id == ((TopicScriptParameter)parameter).TopicId)?.DisplayName; - if (string.IsNullOrEmpty(topicName)) - { - // If the topic has been deleted, we will just display the index in a textbox - TopicSelectButton setUpTopicControlButton = new() { Text = "Select a Topic", ScriptCommand = command, ParameterIndex = i }; + case ScriptParameter.ParameterType.MAP: + MapScriptParameter mapParam = (MapScriptParameter)parameter; + ScriptCommandDropDown mapDropDown = new() { Command = command, ParameterIndex = i }; + mapDropDown.Items.Add(new ListItem { Text = "NONE", Key = "NONE" }); + mapDropDown.Items.AddRange(_project.Items.Where(i => i.Type == ItemDescription.ItemType.Map).Select(i => new ListItem { Text = i.Name, Key = i.Name })); + mapDropDown.SelectedKey = mapParam.Map?.Name ?? "NONE"; + mapDropDown.SelectedKeyChanged += MapDropDown_SelectedKeyChanged; - StackLayout deletedTopicLayout = new() + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, mapDropDown)); + break; + + case ScriptParameter.ParameterType.OPTION: + OptionScriptParameter optionParam = (OptionScriptParameter)parameter; + ScriptCommandDropDown optionDropDown = new() { Command = command, ParameterIndex = i }; + optionDropDown.Items.Add(new ListItem { Text = "NONE", Key = "0" }); + optionDropDown.Items.AddRange(_script.Event.ChoicesSection.Objects.Skip(1).SkipLast(1).Select(c => new ListItem { Text = c.Text.GetSubstitutedString(_project), Key = c.Id.ToString() })); + optionDropDown.SelectedKey = optionParam.Option.Id.ToString(); + optionDropDown.SelectedKeyChanged += OptionDropDown_SelectedKeyChanged; + + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, optionDropDown)); + break; + + case ScriptParameter.ParameterType.PALETTE_EFFECT: + ScriptCommandDropDown paletteEffectDropDown = new() { Command = command, ParameterIndex = i }; + paletteEffectDropDown.Items.AddRange(Enum.GetNames().Select(t => new ListItem { Text = t, Key = t })); + paletteEffectDropDown.SelectedKey = ((PaletteEffectScriptParameter)parameter).Effect.ToString(); + paletteEffectDropDown.SelectedKeyChanged += PaletteEffectDropDown_SelectedKeyChanged; + + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, paletteEffectDropDown)); + break; + + case ScriptParameter.ParameterType.PLACE: + PlaceScriptParameter placeParam = (PlaceScriptParameter)parameter; + CommandGraphicSelectionButton placeSelectionButton = new(placeParam.Place is not null ? placeParam.Place + : NonePreviewableGraphic.PLACE, _tabs, _log) { - Orientation = Orientation.Horizontal, - Items = + Command = command, + ParameterIndex = i, + Project = _project, + }; + placeSelectionButton.Items.Add(NonePreviewableGraphic.PLACE); + placeSelectionButton.Items.AddRange(_project.Items.Where(i => i.Type == ItemDescription.ItemType.Place) + .Select(p => p as IPreviewableGraphic)); + placeSelectionButton.SelectedChanged.Executed += (obj, args) => PlaceSelectionButtonSelectedChanged_Executed(placeSelectionButton, args); + + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, placeSelectionButton)); + break; + + case ScriptParameter.ParameterType.SCREEN: + ScriptCommandScreenSelector screenSelector = new(_log, ((ScreenScriptParameter)parameter).Screen, true) + { + Command = command, + ParameterIndex = i, + CurrentShort = currentShort, + }; + screenSelector.ScreenChanged += ScreenSelector_ScreenChanged; + + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, screenSelector)); + break; + + case ScriptParameter.ParameterType.SCRIPT_SECTION: + ScriptCommandDropDown scriptSectionDropDown = new() { Command = command, ParameterIndex = i, CurrentShort = i }; + if (command.Verb == CommandVerb.VGOTO) + { + scriptSectionDropDown.CurrentShort = 2; + } + scriptSectionDropDown.Items.AddRange(_script.Event.ScriptSections.Where(s => (_script.Event.LabelsSection.Objects.FirstOrDefault(l => l.Name.Replace("/", "") == s.Name)?.Id ?? 0) != 0).Select(s => new ListItem { Text = s.Name, Key = s.Name })); + scriptSectionDropDown.SelectedKey = ((ScriptSectionScriptParameter)parameter)?.Section?.Name ?? "NONE"; + scriptSectionDropDown.SelectedKeyChanged += ScriptSectionDropDown_SelectedKeyChanged; + + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, scriptSectionDropDown)); + break; + + case ScriptParameter.ParameterType.SFX: + ScriptCommandNumericStepper sfxNumericStepper = new() { Command = command, ParameterIndex = i, Value = ((SfxScriptParameter)parameter).SfxIndex }; + sfxNumericStepper.ValueChanged += SfxNumericStepper_ValueChanged; + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, sfxNumericStepper)); + break; + + case ScriptParameter.ParameterType.SFX_MODE: + ScriptCommandDropDown sfxModeDropDown = new() { Command = command, ParameterIndex = i }; + sfxModeDropDown.Items.AddRange(Enum.GetNames().Select(t => new ListItem { Text = t, Key = t })); + sfxModeDropDown.SelectedKey = ((SfxModeScriptParameter)parameter).Mode.ToString(); + sfxModeDropDown.SelectedKeyChanged += SfxModeDropDown_SelectedKeyChanged; + + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, sfxModeDropDown)); + break; + + case ScriptParameter.ParameterType.SHORT: + ScriptCommandNumericStepper shortNumericStepper = new() + { + Command = command, + ParameterIndex = i, + MinValue = short.MinValue, + MaxValue = short.MaxValue, + DecimalPlaces = 0, + Value = ((ShortScriptParameter)parameter).Value + }; + if (command.Verb == CommandVerb.SND_PLAY && parameter.Name == "Crossfade Time (Frames)") + { + shortNumericStepper.SecondIndex = 4; + } + if (command.Verb == CommandVerb.HARUHI_METER) + { + shortNumericStepper.ParameterIndex++; + } + shortNumericStepper.ValueChanged += ShortNumericStepper_ValueChanged; + + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, shortNumericStepper)); + break; + + case ScriptParameter.ParameterType.SPRITE: + SpriteScriptParameter spriteParam = (SpriteScriptParameter)parameter; + CommandGraphicSelectionButton spriteSelectionButton = new(spriteParam.Sprite is not null ? spriteParam.Sprite + : NonePreviewableGraphic.CHARACTER_SPRITE, _tabs, _log) + { + Command = command, + ParameterIndex = i, + Project = _project, + }; + spriteSelectionButton.Items.Add(NonePreviewableGraphic.CHARACTER_SPRITE); + spriteSelectionButton.Items.AddRange(_project.Items.Where(i => i.Type == ItemDescription.ItemType.Character_Sprite).Select(s => (IPreviewableGraphic)s)); + spriteSelectionButton.SelectedChanged.Executed += (obj, args) => SpriteSelectionButton_SelectionMade(spriteSelectionButton, args); + + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, spriteSelectionButton)); + break; + + case ScriptParameter.ParameterType.SPRITE_ENTRANCE: + ScriptCommandDropDown spriteEntranceDropDown = new() { Command = command, ParameterIndex = i }; + spriteEntranceDropDown.Items.AddRange(Enum.GetNames().Select(t => new ListItem { Text = t, Key = t })); + spriteEntranceDropDown.SelectedKey = ((SpriteEntranceScriptParameter)parameter).EntranceTransition.ToString(); + spriteEntranceDropDown.SelectedKeyChanged += SpriteEntranceDropDown_SelectedKeyChanged; + + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, spriteEntranceDropDown)); + break; + + case ScriptParameter.ParameterType.SPRITE_EXIT: + ScriptCommandDropDown spriteExitDropDown = new() { Command = command, ParameterIndex = i }; + spriteExitDropDown.Items.AddRange(Enum.GetNames().Select(t => new ListItem { Text = t, Key = t })); + spriteExitDropDown.SelectedKey = ((SpriteExitScriptParameter)parameter).ExitTransition.ToString(); + spriteExitDropDown.SelectedKeyChanged += SpriteExitDropDown_SelectedKeyChanged; + + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, spriteExitDropDown)); + break; + + case ScriptParameter.ParameterType.SPRITE_SHAKE: + ScriptCommandDropDown spriteShakeDropDown = new() { Command = command, ParameterIndex = i }; + spriteShakeDropDown.Items.AddRange(Enum.GetNames().Select(t => new ListItem { Text = t, Key = t })); + spriteShakeDropDown.SelectedKey = ((SpriteShakeScriptParameter)parameter).ShakeEffect.ToString(); + spriteShakeDropDown.SelectedKeyChanged += SpriteShakeDropDown_SelectedKeyChanged; + + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, spriteShakeDropDown)); + break; + + case ScriptParameter.ParameterType.TEXT_ENTRANCE_EFFECT: + ScriptCommandDropDown textEntranceEffectDropDown = new() { Command = command, ParameterIndex = i }; + textEntranceEffectDropDown.Items.AddRange(Enum.GetNames().Select(t => new ListItem { Text = t, Key = t })); + textEntranceEffectDropDown.SelectedKey = ((TextEntranceEffectScriptParameter)parameter).EntranceEffect.ToString(); + textEntranceEffectDropDown.SelectedKeyChanged += TextEntranceEffectDropDown_SelectedKeyChanged; + + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, textEntranceEffectDropDown)); + break; + + case ScriptParameter.ParameterType.TOPIC: + string topicName = _project.Items.FirstOrDefault(i => i.Type == ItemDescription.ItemType.Topic && + ((TopicItem)i).Topic.Id == ((TopicScriptParameter)parameter).TopicId)?.DisplayName; + if (string.IsNullOrEmpty(topicName)) + { + // If the topic has been deleted, we will just display the index in a textbox + TopicSelectButton setUpTopicControlButton = new() { Text = "Select a Topic", ScriptCommand = command, ParameterIndex = i }; + + StackLayout deletedTopicLayout = new() + { + Orientation = Orientation.Horizontal, + Items = { new TextBox { Text = ((TopicScriptParameter)parameter).TopicId.ToString() }, setUpTopicControlButton, }, - }; + }; - setUpTopicControlButton.Layout = deletedTopicLayout; - setUpTopicControlButton.Click += SetUpTopicControlButton_Click; + setUpTopicControlButton.Layout = deletedTopicLayout; + setUpTopicControlButton.Click += SetUpTopicControlButton_Click; - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, - deletedTopicLayout)); - } - else - { - StackLayout topicLink = ControlGenerator.GetFileLink(_project.Items.FirstOrDefault(i => i.Type == ItemDescription.ItemType.Topic && - ((TopicItem)i).Topic.Id == ((TopicScriptParameter)parameter).TopicId), _tabs, _log); + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, + deletedTopicLayout)); + } + else + { + StackLayout topicLink = ControlGenerator.GetFileLink(_project.Items.FirstOrDefault(i => i.Type == ItemDescription.ItemType.Topic && + ((TopicItem)i).Topic.Id == ((TopicScriptParameter)parameter).TopicId), _tabs, _log); - ScriptCommandDropDown topicDropDown = new() { Command = command, ParameterIndex = i, Link = (ClearableLinkButton)topicLink.Items[1].Control }; - topicDropDown.Items.AddRange(_project.Items.Where(i => i.Type == ItemDescription.ItemType.Topic) - .Select(t => new ListItem { Key = t.DisplayName, Text = t.DisplayName })); - topicDropDown.SelectedKey = topicName; - topicDropDown.SelectedIndexChanged += TopicDropDown_SelectedIndexChanged; + ScriptCommandDropDown topicDropDown = new() { Command = command, ParameterIndex = i, Link = (ClearableLinkButton)topicLink.Items[1].Control }; + topicDropDown.Items.AddRange(_project.Items.Where(i => i.Type == ItemDescription.ItemType.Topic) + .Select(t => new ListItem { Key = t.DisplayName, Text = t.DisplayName })); + topicDropDown.SelectedKey = topicName; + topicDropDown.SelectedIndexChanged += TopicDropDown_SelectedIndexChanged; - StackLayout topicLinkLayout = new() - { - Orientation = Orientation.Horizontal, - Items = + StackLayout topicLinkLayout = new() + { + Orientation = Orientation.Horizontal, + Items = { topicDropDown, topicLink, }, - }; + }; - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, - topicLinkLayout)); - } - break; + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, + topicLinkLayout)); + } + break; - case ScriptParameter.ParameterType.TRANSITION: - ScriptCommandDropDown transitionDropDown = new() { Command = command, ParameterIndex = i }; - transitionDropDown.Items.AddRange(Enum.GetNames().Select(t => new ListItem { Text = t, Key = t })); - transitionDropDown.SelectedKey = ((TransitionScriptParameter)parameter).Transition.ToString(); - transitionDropDown.SelectedKeyChanged += TransitionDropDown_SelectedKeyChanged; + case ScriptParameter.ParameterType.TRANSITION: + ScriptCommandDropDown transitionDropDown = new() { Command = command, ParameterIndex = i }; + transitionDropDown.Items.AddRange(Enum.GetNames().Select(t => new ListItem { Text = t, Key = t })); + transitionDropDown.SelectedKey = ((TransitionScriptParameter)parameter).Transition.ToString(); + transitionDropDown.SelectedKeyChanged += TransitionDropDown_SelectedKeyChanged; - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( - ControlGenerator.GetControlWithLabel(parameter.Name, transitionDropDown)); - break; + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add( + ControlGenerator.GetControlWithLabel(parameter.Name, transitionDropDown)); + break; - case ScriptParameter.ParameterType.VOICE_LINE: - VoicedLineScriptParameter vceParam = (VoicedLineScriptParameter)parameter; - StackLayout vceLink = ControlGenerator.GetFileLink(vceParam.VoiceLine is not null ? vceParam.VoiceLine : NoneItem.VOICE, _tabs, _log); + case ScriptParameter.ParameterType.VOICE_LINE: + VoicedLineScriptParameter vceParam = (VoicedLineScriptParameter)parameter; + StackLayout vceLink = ControlGenerator.GetFileLink(vceParam.VoiceLine is not null ? vceParam.VoiceLine : NoneItem.VOICE, _tabs, _log); - ScriptCommandDropDown vceDropDown = new() { Command = command, ParameterIndex = i, Link = (ClearableLinkButton)vceLink.Items[1].Control }; - vceDropDown.Items.Add(new ListItem { Key = "NONE", Text = "NONE" }); - vceDropDown.Items.AddRange(_project.Items.Where(i => i.Type == ItemDescription.ItemType.Voice).Select(i => new ListItem { Text = i.Name, Key = i.Name })); - vceDropDown.SelectedKey = vceParam.VoiceLine?.Name ?? "NONE"; - vceDropDown.SelectedKeyChanged += VceDropDown_SelectedKeyChanged; + ScriptCommandDropDown vceDropDown = new() { Command = command, ParameterIndex = i, Link = (ClearableLinkButton)vceLink.Items[1].Control }; + vceDropDown.Items.Add(new ListItem { Key = "NONE", Text = "NONE" }); + vceDropDown.Items.AddRange(_project.Items.Where(i => i.Type == ItemDescription.ItemType.Voice).Select(i => new ListItem { Text = i.Name, Key = i.Name })); + vceDropDown.SelectedKey = vceParam.VoiceLine?.Name ?? "NONE"; + vceDropDown.SelectedKeyChanged += VceDropDown_SelectedKeyChanged; - StackLayout vceLayout = new() - { - Orientation = Orientation.Horizontal, - VerticalContentAlignment = VerticalAlignment.Center, - Items = + StackLayout vceLayout = new() + { + Orientation = Orientation.Horizontal, + VerticalContentAlignment = VerticalAlignment.Center, + Items = { ControlGenerator.GetControlWithLabel(parameter.Name, vceDropDown), vceLink, } - }; + }; - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add(vceLayout); - break; + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows[0].Cells.Add(vceLayout); + break; - default: - _log.LogError($"Invalid parameter detected in script {_script.Name} parameter {parameter.Name}"); - break; - } - currentCol++; - if (currentCol >= cols) - { - currentCol = 0; - currentRow++; - controlsTable.Rows.Add(new(new TableLayout { Spacing = new Size(5, 5) })); - ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows.Add(new()); + default: + _log.LogError($"Invalid parameter detected in script {_script.Name} parameter {parameter.Name}"); + break; + } + currentCol++; + if (currentCol >= cols) + { + currentCol = 0; + currentRow++; + controlsTable.Rows.Add(new(new TableLayout { Spacing = new Size(5, 5) })); + ((TableLayout)controlsTable.Rows.Last().Cells[0].Control).Rows.Add(new()); + } + currentShort++; } - currentShort++; - } - _editorControls.Items.Add(new StackLayoutItem(controlsTable, expand: true)); + _editorControls.Items.Add(new StackLayoutItem(controlsTable, expand: true)); + } + catch (Exception ex) + { + _log.LogException("Failed to load editor controls!", ex); + } } private void UpdatePreview() { - _preview.Items.Clear(); - - SKBitmap previewBitmap = new(256, 384); - SKCanvas canvas = new(previewBitmap); - canvas.DrawColor(SKColors.Black); - - ScriptItemCommand currentCommand = ((ScriptCommandSectionEntry)_commandsPanel.Viewer.SelectedItem)?.Command; - if (currentCommand is not null) + try { - List commands = currentCommand.WalkCommandGraph(_commands, _script.Graph); + _preview.Items.Clear(); - if (commands is null) - { - _log.LogWarning($"No path found to current command."); - using Stream noPreviewStream = Assembly.GetCallingAssembly().GetManifestResourceStream("SerialLoops.Graphics.ScriptPreviewError.png"); - canvas.DrawImage(SKImage.FromEncodedData(noPreviewStream), new SKPoint(0, 0)); - canvas.Flush(); - _preview.Items.Add(new SKGuiImage(previewBitmap)); - return; - } + SKBitmap previewBitmap = new(256, 384); + SKCanvas canvas = new(previewBitmap); + canvas.DrawColor(SKColors.Black); - if (commands.Any(c => c.Verb == CommandVerb.EPHEADER) - && ((EpisodeHeaderScriptParameter)commands.Last(c => c.Verb == CommandVerb.EPHEADER).Parameters[0]) - .EpisodeHeaderIndex != EpisodeHeaderScriptParameter.Episode.None) + ScriptItemCommand currentCommand = ((ScriptCommandSectionEntry)_commandsPanel.Viewer.SelectedItem)?.Command; + if (currentCommand is not null) { - // We use names here because display names can change but names cannot - SystemTextureItem systexItem = ((EpisodeHeaderScriptParameter)commands.Last(c => c.Verb == CommandVerb.EPHEADER).Parameters[0]).EpisodeHeaderIndex switch + List commands = currentCommand.WalkCommandGraph(_commands, _script.Graph); + + if (commands is null) { - EpisodeHeaderScriptParameter.Episode.EPISODE_1 => - (SystemTextureItem)_project.Items.First(i => i.Name == "SYSTEX_SYS_CMN_T60"), - EpisodeHeaderScriptParameter.Episode.EPISODE_2 => - (SystemTextureItem)_project.Items.First(i => i.Name == "SYSTEX_SYS_CMN_T61"), - EpisodeHeaderScriptParameter.Episode.EPISODE_3 => - (SystemTextureItem)_project.Items.First(i => i.Name == "SYSTEX_SYS_CMN_T62"), - EpisodeHeaderScriptParameter.Episode.EPISODE_4 => - (SystemTextureItem)_project.Items.First(i => i.Name == "SYSTEX_SYS_CMN_T63"), - EpisodeHeaderScriptParameter.Episode.EPISODE_5 => - (SystemTextureItem)_project.Items.First(i => i.Name == "SYSTEX_SYS_CMN_T64"), - EpisodeHeaderScriptParameter.Episode.EPILOGUE => - (SystemTextureItem)_project.Items.First(i => i.Name == "SYSTEX_SYS_CMN_T66"), - _ => null, - }; + _log.LogWarning($"No path found to current command."); + using Stream noPreviewStream = Assembly.GetCallingAssembly().GetManifestResourceStream("SerialLoops.Graphics.ScriptPreviewError.png"); + canvas.DrawImage(SKImage.FromEncodedData(noPreviewStream), new SKPoint(0, 0)); + canvas.Flush(); + _preview.Items.Add(new SKGuiImage(previewBitmap)); + return; + } - canvas.DrawBitmap(systexItem.GetTexture(), new SKPoint(0, 0)); - } - else - { - // Draw top screen "kinetic" background - for (int i = commands.Count - 1; i >= 0; i--) + if (commands.Any(c => c.Verb == CommandVerb.EPHEADER) + && ((EpisodeHeaderScriptParameter)commands.Last(c => c.Verb == CommandVerb.EPHEADER).Parameters[0]) + .EpisodeHeaderIndex != EpisodeHeaderScriptParameter.Episode.None) { - if (commands[i].Verb == CommandVerb.KBG_DISP && ((BgScriptParameter)commands[i].Parameters[0]).Background is not null) + // We use names here because display names can change but names cannot + SystemTextureItem systexItem = ((EpisodeHeaderScriptParameter)commands.Last(c => c.Verb == CommandVerb.EPHEADER).Parameters[0]).EpisodeHeaderIndex switch { - canvas.DrawBitmap(((BgScriptParameter)commands[i].Parameters[0]).Background.GetBackground(), new SKPoint(0, 0)); - break; - } - } + EpisodeHeaderScriptParameter.Episode.EPISODE_1 => + (SystemTextureItem)_project.Items.First(i => i.Name == "SYSTEX_SYS_CMN_T60"), + EpisodeHeaderScriptParameter.Episode.EPISODE_2 => + (SystemTextureItem)_project.Items.First(i => i.Name == "SYSTEX_SYS_CMN_T61"), + EpisodeHeaderScriptParameter.Episode.EPISODE_3 => + (SystemTextureItem)_project.Items.First(i => i.Name == "SYSTEX_SYS_CMN_T62"), + EpisodeHeaderScriptParameter.Episode.EPISODE_4 => + (SystemTextureItem)_project.Items.First(i => i.Name == "SYSTEX_SYS_CMN_T63"), + EpisodeHeaderScriptParameter.Episode.EPISODE_5 => + (SystemTextureItem)_project.Items.First(i => i.Name == "SYSTEX_SYS_CMN_T64"), + EpisodeHeaderScriptParameter.Episode.EPILOGUE => + (SystemTextureItem)_project.Items.First(i => i.Name == "SYSTEX_SYS_CMN_T66"), + _ => null, + }; - // Draw Place - for (int i = commands.Count - 1; i >= 0; i--) + canvas.DrawBitmap(systexItem.GetTexture(), new SKPoint(0, 0)); + } + else { - if (commands[i].Verb == CommandVerb.SET_PLACE) + // Draw top screen "kinetic" background + for (int i = commands.Count - 1; i >= 0; i--) { - if (((BoolScriptParameter)commands[i].Parameters[0]).Value && (((PlaceScriptParameter)commands[i].Parameters[1]).Place is not null)) + if (commands[i].Verb == CommandVerb.KBG_DISP && ((BgScriptParameter)commands[i].Parameters[0]).Background is not null) { - canvas.DrawBitmap(((PlaceScriptParameter)commands[i].Parameters[1]).Place.GetPreview(_project), new SKPoint(5, 40)); + canvas.DrawBitmap(((BgScriptParameter)commands[i].Parameters[0]).Background.GetBackground(), new SKPoint(0, 0)); + break; + } + } + + // Draw Place + for (int i = commands.Count - 1; i >= 0; i--) + { + if (commands[i].Verb == CommandVerb.SET_PLACE) + { + if (((BoolScriptParameter)commands[i].Parameters[0]).Value && (((PlaceScriptParameter)commands[i].Parameters[1]).Place is not null)) + { + canvas.DrawBitmap(((PlaceScriptParameter)commands[i].Parameters[1]).Place.GetPreview(_project), new SKPoint(5, 40)); + } + break; } - break; } - } - // Draw top screen chibis - List chibis = new(); + // Draw top screen chibis + List chibis = new(); - foreach (StartingChibiEntry chibi in _script.Event.StartingChibisSection?.Objects ?? new List()) - { - if (chibi.ChibiIndex > 0) + foreach (StartingChibiEntry chibi in _script.Event.StartingChibisSection?.Objects ?? new List()) { - chibis.Add((ChibiItem)_project.Items.First(i => i.Type == ItemDescription.ItemType.Chibi && ((ChibiItem)i).ChibiIndex == chibi.ChibiIndex)); + if (chibi.ChibiIndex > 0) + { + chibis.Add((ChibiItem)_project.Items.First(i => i.Type == ItemDescription.ItemType.Chibi && ((ChibiItem)i).ChibiIndex == chibi.ChibiIndex)); + } } - } - for (int i = 0; i < commands.Count; i++) - { - if (commands[i].Verb == CommandVerb.CHIBI_ENTEREXIT) + for (int i = 0; i < commands.Count; i++) { - if (((ChibiEnterExitScriptParameter)commands[i].Parameters[1]).Mode == ChibiEnterExitScriptParameter.ChibiEnterExitType.ENTER) + if (commands[i].Verb == CommandVerb.CHIBI_ENTEREXIT) { - ChibiItem chibi = ((ChibiScriptParameter)commands[i].Parameters[0]).Chibi; - if (!chibis.Contains(chibi)) + if (((ChibiEnterExitScriptParameter)commands[i].Parameters[1]).Mode == ChibiEnterExitScriptParameter.ChibiEnterExitType.ENTER) { - if (chibi.ChibiIndex < 1 || chibis.Count == 0) - { - chibis.Add(chibi); - } - else + ChibiItem chibi = ((ChibiScriptParameter)commands[i].Parameters[0]).Chibi; + if (!chibis.Contains(chibi)) { - bool inserted = false; - for (int j = 0; j < chibis.Count; j++) + if (chibi.ChibiIndex < 1 || chibis.Count == 0) { - if (chibis[j].ChibiIndex > chibi.ChibiIndex) - { - chibis.Insert(j, chibi); - inserted = true; - break; - } + chibis.Add(chibi); } - if (!inserted) + else { - chibis.Add(chibi); + bool inserted = false; + for (int j = 0; j < chibis.Count; j++) + { + if (chibis[j].ChibiIndex > chibi.ChibiIndex) + { + chibis.Insert(j, chibi); + inserted = true; + break; + } + } + if (!inserted) + { + chibis.Add(chibi); + } } } + else + { + _log.LogWarning($"Chibi {chibi.Name} set to join, but already was present"); + } } else { - _log.LogWarning($"Chibi {chibi.Name} set to join, but already was present"); - } - } - else - { - try - { - chibis.Remove(((ChibiScriptParameter)commands[i].Parameters[0]).Chibi); - } - catch (Exception) - { - _log.LogWarning($"Chibi set to leave was not present."); + try + { + chibis.Remove(((ChibiScriptParameter)commands[i].Parameters[0]).Chibi); + } + catch (Exception) + { + _log.LogWarning($"Chibi set to leave was not present."); + } } } } - } - - int chibiStartX, chibiY; - if (commands.Any(c => c.Verb == EventFile.CommandVerb.OP_MODE)) - { - chibiStartX = 100; - chibiY = 50; - } - else - { - chibiStartX = 44; - chibiY = 100; - } - int chibiCurrentX = chibiStartX; - int chibiWidth = 0; - foreach (ChibiItem chibi in chibis) - { - SKBitmap chibiFrame = chibi.ChibiAnimations.First().Value.ElementAt(0).Frame; - canvas.DrawBitmap(chibiFrame, new SKPoint(chibiCurrentX, chibiY)); - chibiWidth = chibiFrame.Width - 10; - chibiCurrentX += chibiWidth; - } - // Draw top screen chibi emotes - if (currentCommand.Verb == CommandVerb.CHIBI_EMOTE) - { - ChibiItem chibi = ((ChibiScriptParameter)currentCommand.Parameters[0]).Chibi; - if (chibis.Contains(chibi)) + int chibiStartX, chibiY; + if (commands.Any(c => c.Verb == EventFile.CommandVerb.OP_MODE)) { - int chibiIndex = chibis.IndexOf(chibi); - SKBitmap emotes = _project.Grp.Files.First(f => f.Name == "SYS_ADV_T08DNX").GetImage(width: 32, transparentIndex: 0); - int internalYOffset = ((int)((ChibiEmoteScriptParameter)currentCommand.Parameters[1]).Emote - 1) * 32; - int externalXOffset = chibiStartX + chibiWidth * chibiIndex; - canvas.DrawBitmap(emotes, new SKRect(0, internalYOffset, 32, internalYOffset + 32), new SKRect(externalXOffset + 16, chibiY - 32, externalXOffset + 48, chibiY)); + chibiStartX = 100; + chibiY = 50; } else { - _log.LogWarning($"Chibi {chibi.Name} not currently on screen; cannot display emote."); + chibiStartX = 44; + chibiY = 100; + } + int chibiCurrentX = chibiStartX; + int chibiWidth = 0; + foreach (ChibiItem chibi in chibis) + { + SKBitmap chibiFrame = chibi.ChibiAnimations.First().Value.ElementAt(0).Frame; + canvas.DrawBitmap(chibiFrame, new SKPoint(chibiCurrentX, chibiY)); + chibiWidth = chibiFrame.Width - 10; + chibiCurrentX += chibiWidth; } - } - } - - // Draw background - bool bgReverted = false; - ScriptItemCommand palCommand = commands.LastOrDefault(c => c.Verb == CommandVerb.PALEFFECT); - ScriptItemCommand lastBgCommand = commands.LastOrDefault(c => c.Verb == CommandVerb.BG_DISP || - c.Verb == CommandVerb.BG_DISP2 || c.Verb == CommandVerb.BG_DISPCG || c.Verb == CommandVerb.BG_FADE || - c.Verb == CommandVerb.BG_REVERT); - SKPaint palEffectPaint = PaletteEffectScriptParameter.IdentityPaint; - if (palCommand is not null && lastBgCommand is not null && commands.IndexOf(palCommand) > commands.IndexOf(lastBgCommand)) - { - switch (((PaletteEffectScriptParameter)palCommand.Parameters[0]).Effect) - { - case PaletteEffectScriptParameter.PaletteEffect.INVERTED: - palEffectPaint = PaletteEffectScriptParameter.InvertedPaint; - break; - - case PaletteEffectScriptParameter.PaletteEffect.GRAYSCALE: - palEffectPaint = PaletteEffectScriptParameter.GrayscalePaint; - break; - - case PaletteEffectScriptParameter.PaletteEffect.SEPIA: - palEffectPaint = PaletteEffectScriptParameter.SepiaPaint; - break; - - case PaletteEffectScriptParameter.PaletteEffect.DIMMED: - palEffectPaint = PaletteEffectScriptParameter.DimmedPaint; - break; - } - } - ScriptItemCommand bgScrollCommand = null; - for (int i = commands.Count - 1; i >= 0; i--) - { - if (commands[i].Verb == CommandVerb.BG_REVERT) - { - bgReverted = true; - continue; - } - if (commands[i].Verb == CommandVerb.BG_SCROLL) - { - bgScrollCommand = commands[i]; - continue; - } - // Checks to see if this is one of the commands that sets a BG_REVERT immune background or if BG_REVERT hasn't been called - if (commands[i].Verb == CommandVerb.BG_DISP || commands[i].Verb == CommandVerb.BG_DISP2 || - (commands[i].Verb == CommandVerb.BG_FADE && (((BgScriptParameter)commands[i].Parameters[0]).Background is not null)) || - (!bgReverted && (commands[i].Verb == CommandVerb.BG_DISPCG || commands[i].Verb == CommandVerb.BG_FADE))) - { - BackgroundItem background = (commands[i].Verb == CommandVerb.BG_FADE && ((BgScriptParameter)commands[i].Parameters[0]).Background is null) ? - ((BgScriptParameter)commands[i].Parameters[1]).Background : ((BgScriptParameter)commands[i].Parameters[0]).Background; - if (background is not null) + // Draw top screen chibi emotes + if (currentCommand.Verb == CommandVerb.CHIBI_EMOTE) { - switch (background.BackgroundType) + ChibiItem chibi = ((ChibiScriptParameter)currentCommand.Parameters[0]).Chibi; + if (chibis.Contains(chibi)) { - case BgType.TEX_CG_DUAL_SCREEN: - SKBitmap dualScreenBg = background.GetBackground(); - if (bgScrollCommand is not null && ((BgScrollDirectionScriptParameter)bgScrollCommand.Parameters[0]).ScrollDirection == BgScrollDirectionScriptParameter.BgScrollDirection.DOWN) - { - canvas.DrawBitmap(dualScreenBg, new SKRect(0, background.Graphic2.Height - 192, 256, background.Graphic2.Height), new SKRect(0, 0, 256, 192)); - int bottomScreenX = dualScreenBg.Height - 192; - canvas.DrawBitmap(dualScreenBg, new SKRect(0, bottomScreenX, 256, bottomScreenX + 192), new SKRect(0, 192, 256, 384)); - } - else - { - canvas.DrawBitmap(dualScreenBg, new SKRect(0, 0, 256, 192), new SKRect(0, 0, 256, 192)); - canvas.DrawBitmap(dualScreenBg, new SKRect(0, background.Graphic2.Height, 256, background.Graphic2.Height + 192), new SKRect(0, 192, 256, 384)); - } - break; - - case BgType.TEX_CG_SINGLE: - if (((BoolScriptParameter)commands[i].Parameters[1]).Value - || (bgScrollCommand is not null && ((BgScrollDirectionScriptParameter)bgScrollCommand.Parameters[0]).ScrollDirection == BgScrollDirectionScriptParameter.BgScrollDirection.DOWN)) - { - SKBitmap bgBitmap = background.GetBackground(); - canvas.DrawBitmap(bgBitmap, new SKRect(0, bgBitmap.Height - 192, bgBitmap.Width, bgBitmap.Height), - new SKRect(0, 192, 256, 384)); - } - else - { - canvas.DrawBitmap(background.GetBackground(), new SKPoint(0, 192)); - } - break; - - case BgType.TEX_CG_WIDE: - if (bgScrollCommand is not null && ((BgScrollDirectionScriptParameter)bgScrollCommand.Parameters[0]).ScrollDirection == BgScrollDirectionScriptParameter.BgScrollDirection.RIGHT) - { - SKBitmap bgBitmap = background.GetBackground(); - canvas.DrawBitmap(bgBitmap, new SKRect(bgBitmap.Width - 256, 0, bgBitmap.Width, 192), new SKRect(0, 192, 256, 384)); - } - else - { - canvas.DrawBitmap(background.GetBackground(), new SKPoint(0, 192)); - } - break; - - case BgType.TEX_CG: - canvas.DrawBitmap(background.GetBackground(), new SKPoint(0, 192)); - break; - - default: - canvas.DrawBitmap(background.GetBackground(), new SKPoint(0, 192), palEffectPaint); - break; + int chibiIndex = chibis.IndexOf(chibi); + SKBitmap emotes = _project.Grp.Files.First(f => f.Name == "SYS_ADV_T08DNX").GetImage(width: 32, transparentIndex: 0); + int internalYOffset = ((int)((ChibiEmoteScriptParameter)currentCommand.Parameters[1]).Emote - 1) * 32; + int externalXOffset = chibiStartX + chibiWidth * chibiIndex; + canvas.DrawBitmap(emotes, new SKRect(0, internalYOffset, 32, internalYOffset + 32), new SKRect(externalXOffset + 16, chibiY - 32, externalXOffset + 48, chibiY)); + } + else + { + _log.LogWarning($"Chibi {chibi.Name} not currently on screen; cannot display emote."); } - break; } } - } - - // Draw character sprites - Dictionary sprites = new(); - Dictionary previousSprites = new(); - ScriptItemCommand previousCommand = null; - foreach (ScriptItemCommand command in commands) - { - if (previousCommand?.Verb == CommandVerb.DIALOGUE) + // Draw background + bool bgReverted = false; + ScriptItemCommand palCommand = commands.LastOrDefault(c => c.Verb == CommandVerb.PALEFFECT); + ScriptItemCommand lastBgCommand = commands.LastOrDefault(c => c.Verb == CommandVerb.BG_DISP || + c.Verb == CommandVerb.BG_DISP2 || c.Verb == CommandVerb.BG_DISPCG || c.Verb == CommandVerb.BG_FADE || + c.Verb == CommandVerb.BG_REVERT); + SKPaint palEffectPaint = PaletteEffectScriptParameter.IdentityPaint; + if (palCommand is not null && lastBgCommand is not null && commands.IndexOf(palCommand) > commands.IndexOf(lastBgCommand)) { - SpriteExitScriptParameter spriteExitMoveParam = (SpriteExitScriptParameter)previousCommand?.Parameters[3]; // exits/moves happen _after_ dialogue is advanced, so we check these at this point - if (spriteExitMoveParam.ExitTransition != SpriteExitScriptParameter.SpriteExitTransition.NO_EXIT) + switch (((PaletteEffectScriptParameter)palCommand.Parameters[0]).Effect) { - CharacterItem prevCharacter = (CharacterItem)_project.Items.First(i => i.Type == ItemDescription.ItemType.Character && - i.Name == $"CHR_{_project.Characters[(int)((DialogueScriptParameter)previousCommand.Parameters[0]).Line.Speaker].Name}"); - SpriteScriptParameter previousSpriteParam = (SpriteScriptParameter)previousCommand.Parameters[1]; - short layer = ((ShortScriptParameter)previousCommand.Parameters[9]).Value; - switch (spriteExitMoveParam.ExitTransition) - { - case SpriteExitScriptParameter.SpriteExitTransition.SLIDE_LEFT_TO_LEFT_FADE_OUT: - case SpriteExitScriptParameter.SpriteExitTransition.SLIDE_LEFT_TO_RIGHT_FADE_OUT: - case SpriteExitScriptParameter.SpriteExitTransition.SLIDE_FROM_CENTER_TO_LEFT_FADE_OUT: - case SpriteExitScriptParameter.SpriteExitTransition.SLIDE_FROM_CENTER_TO_RIGHT_FADE_OUT: - case SpriteExitScriptParameter.SpriteExitTransition.FADE_OUT_CENTER: - case SpriteExitScriptParameter.SpriteExitTransition.FADE_OUT_LEFT: - if (sprites.ContainsKey(prevCharacter) && previousSprites.ContainsKey(prevCharacter) && ((SpriteScriptParameter)previousCommand.Parameters[1]).Sprite?.Sprite?.Character == prevCharacter.MessageInfo.Character) - { - sprites.Remove(prevCharacter); - previousSprites.Remove(prevCharacter); - } - break; + case PaletteEffectScriptParameter.PaletteEffect.INVERTED: + palEffectPaint = PaletteEffectScriptParameter.InvertedPaint; + break; - case SpriteExitScriptParameter.SpriteExitTransition.SLIDE_CENTER_TO_LEFT_AND_STAY: - case SpriteExitScriptParameter.SpriteExitTransition.SLIDE_RIGHT_TO_LEFT_AND_STAY: - sprites[prevCharacter] = new() { Sprite = previousSpriteParam.Sprite, Positioning = new() { Position = SpritePositioning.SpritePosition.LEFT, Layer = layer } }; - break; + case PaletteEffectScriptParameter.PaletteEffect.GRAYSCALE: + palEffectPaint = PaletteEffectScriptParameter.GrayscalePaint; + break; - case SpriteExitScriptParameter.SpriteExitTransition.SLIDE_CENTER_TO_RIGHT_AND_STAY: - case SpriteExitScriptParameter.SpriteExitTransition.SLIDE_LEFT_TO_RIGHT_AND_STAY: - sprites[prevCharacter] = new() { Sprite = previousSpriteParam.Sprite, Positioning = new() { Position = SpritePositioning.SpritePosition.RIGHT, Layer = layer } }; - break; - } + case PaletteEffectScriptParameter.PaletteEffect.SEPIA: + palEffectPaint = PaletteEffectScriptParameter.SepiaPaint; + break; + + case PaletteEffectScriptParameter.PaletteEffect.DIMMED: + palEffectPaint = PaletteEffectScriptParameter.DimmedPaint; + break; } } - if (command.Verb == CommandVerb.DIALOGUE) + ScriptItemCommand bgScrollCommand = null; + for (int i = commands.Count - 1; i >= 0; i--) { - SpriteScriptParameter spriteParam = (SpriteScriptParameter)command.Parameters[1]; - SKPaint spritePaint = PaletteEffectScriptParameter.IdentityPaint; - if (commands.IndexOf(palCommand) > commands.IndexOf(command)) + if (commands[i].Verb == CommandVerb.BG_REVERT) { - spritePaint = ((PaletteEffectScriptParameter)palCommand.Parameters[0]).Effect switch - { - PaletteEffectScriptParameter.PaletteEffect.INVERTED => PaletteEffectScriptParameter.InvertedPaint, - PaletteEffectScriptParameter.PaletteEffect.GRAYSCALE => PaletteEffectScriptParameter.GrayscalePaint, - PaletteEffectScriptParameter.PaletteEffect.SEPIA => PaletteEffectScriptParameter.SepiaPaint, - PaletteEffectScriptParameter.PaletteEffect.DIMMED => PaletteEffectScriptParameter.DimmedPaint, - _ => PaletteEffectScriptParameter.IdentityPaint, - }; + bgReverted = true; + continue; } - if (spriteParam.Sprite is not null) + if (commands[i].Verb == CommandVerb.BG_SCROLL) { - CharacterItem character; - try - { - character = (CharacterItem)_project.Items.First(i => i.Type == ItemDescription.ItemType.Character && - i.DisplayName == $"CHR_{_project.Characters[(int)((DialogueScriptParameter)command.Parameters[0]).Line.Speaker].Name}"); - } - catch (InvalidOperationException) - { - _log.LogWarning($"Unable to determine speaking character in DIALOGUE command in {_script.DisplayName}."); - using Stream noPreviewStream = Assembly.GetCallingAssembly().GetManifestResourceStream("SerialLoops.Graphics.ScriptPreviewError.png"); - canvas.DrawImage(SKImage.FromEncodedData(noPreviewStream), new SKPoint(0, 0)); - canvas.Flush(); - _preview.Items.Add(new SKGuiImage(previewBitmap)); - return; - } - SpriteEntranceScriptParameter spriteEntranceParam = (SpriteEntranceScriptParameter)command.Parameters[2]; - short layer = ((ShortScriptParameter)command.Parameters[9]).Value; + bgScrollCommand = commands[i]; + continue; + } + // Checks to see if this is one of the commands that sets a BG_REVERT immune background or if BG_REVERT hasn't been called + if (commands[i].Verb == CommandVerb.BG_DISP || commands[i].Verb == CommandVerb.BG_DISP2 || + (commands[i].Verb == CommandVerb.BG_FADE && (((BgScriptParameter)commands[i].Parameters[0]).Background is not null)) || + (!bgReverted && (commands[i].Verb == CommandVerb.BG_DISPCG || commands[i].Verb == CommandVerb.BG_FADE))) + { + BackgroundItem background = (commands[i].Verb == CommandVerb.BG_FADE && ((BgScriptParameter)commands[i].Parameters[0]).Background is null) ? + ((BgScriptParameter)commands[i].Parameters[1]).Background : ((BgScriptParameter)commands[i].Parameters[0]).Background; - if (!sprites.ContainsKey(character) && spriteEntranceParam.EntranceTransition != SpriteEntranceScriptParameter.SpriteEntranceTransition.NO_TRANSITION) - { - sprites.Add(character, new()); - previousSprites.Add(character, new()); - } - if (sprites.ContainsKey(character)) - { - previousSprites[character] = sprites[character]; - } - if (spriteEntranceParam.EntranceTransition != SpriteEntranceScriptParameter.SpriteEntranceTransition.NO_TRANSITION) + if (background is not null) { - switch (spriteEntranceParam.EntranceTransition) + switch (background.BackgroundType) { - case SpriteEntranceScriptParameter.SpriteEntranceTransition.FADE_TO_CENTER: - case SpriteEntranceScriptParameter.SpriteEntranceTransition.SLIDE_LEFT_TO_CENTER: - case SpriteEntranceScriptParameter.SpriteEntranceTransition.SLIDE_RIGHT_TO_CENTER: - sprites[character] = new() { Sprite = spriteParam.Sprite, Positioning = new() { Position = SpritePositioning.SpritePosition.CENTER, Layer = layer }, PalEffect = spritePaint }; + case BgType.TEX_CG_DUAL_SCREEN: + SKBitmap dualScreenBg = background.GetBackground(); + if (bgScrollCommand is not null && ((BgScrollDirectionScriptParameter)bgScrollCommand.Parameters[0]).ScrollDirection == BgScrollDirectionScriptParameter.BgScrollDirection.DOWN) + { + canvas.DrawBitmap(dualScreenBg, new SKRect(0, background.Graphic2.Height - 192, 256, background.Graphic2.Height), new SKRect(0, 0, 256, 192)); + int bottomScreenX = dualScreenBg.Height - 192; + canvas.DrawBitmap(dualScreenBg, new SKRect(0, bottomScreenX, 256, bottomScreenX + 192), new SKRect(0, 192, 256, 384)); + } + else + { + canvas.DrawBitmap(dualScreenBg, new SKRect(0, 0, 256, 192), new SKRect(0, 0, 256, 192)); + canvas.DrawBitmap(dualScreenBg, new SKRect(0, background.Graphic2.Height, 256, background.Graphic2.Height + 192), new SKRect(0, 192, 256, 384)); + } + break; + + case BgType.TEX_CG_SINGLE: + if (((BoolScriptParameter)commands[i].Parameters[1]).Value + || (bgScrollCommand is not null && ((BgScrollDirectionScriptParameter)bgScrollCommand.Parameters[0]).ScrollDirection == BgScrollDirectionScriptParameter.BgScrollDirection.DOWN)) + { + SKBitmap bgBitmap = background.GetBackground(); + canvas.DrawBitmap(bgBitmap, new SKRect(0, bgBitmap.Height - 192, bgBitmap.Width, bgBitmap.Height), + new SKRect(0, 192, 256, 384)); + } + else + { + canvas.DrawBitmap(background.GetBackground(), new SKPoint(0, 192)); + } + break; + + case BgType.TEX_CG_WIDE: + if (bgScrollCommand is not null && ((BgScrollDirectionScriptParameter)bgScrollCommand.Parameters[0]).ScrollDirection == BgScrollDirectionScriptParameter.BgScrollDirection.RIGHT) + { + SKBitmap bgBitmap = background.GetBackground(); + canvas.DrawBitmap(bgBitmap, new SKRect(bgBitmap.Width - 256, 0, bgBitmap.Width, 192), new SKRect(0, 192, 256, 384)); + } + else + { + canvas.DrawBitmap(background.GetBackground(), new SKPoint(0, 192)); + } break; - case SpriteEntranceScriptParameter.SpriteEntranceTransition.FADE_IN_LEFT: - case SpriteEntranceScriptParameter.SpriteEntranceTransition.PEEK_RIGHT_TO_LEFT: - case SpriteEntranceScriptParameter.SpriteEntranceTransition.SLIDE_RIGHT_TO_LEFT: - case SpriteEntranceScriptParameter.SpriteEntranceTransition.SLIDE_RIGHT_TO_LEFT_FAST: - case SpriteEntranceScriptParameter.SpriteEntranceTransition.SLIDE_RIGHT_TO_LEFT_SLOW: - sprites[character] = new() { Sprite = spriteParam.Sprite, Positioning = new() { Position = SpritePositioning.SpritePosition.LEFT, Layer = layer }, PalEffect = spritePaint }; + case BgType.TEX_CG: + canvas.DrawBitmap(background.GetBackground(), new SKPoint(0, 192)); break; - case SpriteEntranceScriptParameter.SpriteEntranceTransition.SLIDE_LEFT_TO_RIGHT: - case SpriteEntranceScriptParameter.SpriteEntranceTransition.SLIDE_LEFT_TO_RIGHT_FAST: - case SpriteEntranceScriptParameter.SpriteEntranceTransition.SLIDE_LEFT_TO_RIGHT_SLOW: - sprites[character] = new() { Sprite = spriteParam.Sprite, Positioning = new() { Position = SpritePositioning.SpritePosition.RIGHT, Layer = layer }, PalEffect = spritePaint }; + default: + canvas.DrawBitmap(background.GetBackground(), new SKPoint(0, 192), palEffectPaint); break; } - } - else if (sprites.ContainsKey(character)) - { - sprites[character] = new() { Sprite = spriteParam.Sprite, Positioning = sprites[character].Positioning, PalEffect = spritePaint }; + break; } } } - else if (command.Verb == CommandVerb.INVEST_START) - { - sprites.Clear(); - previousSprites.Clear(); - } - previousCommand = command; - } - foreach (PositionedSprite sprite in sprites.Values.OrderBy(p => p.Positioning.Layer)) - { - SKBitmap spriteBitmap = sprite.Sprite.GetClosedMouthAnimation(_project)[0].Frame; - canvas.DrawBitmap(spriteBitmap, sprite.Positioning.GetSpritePosition(spriteBitmap), sprite.PalEffect); - } + // Draw character sprites + Dictionary sprites = new(); + Dictionary previousSprites = new(); - // Draw dialogue - ScriptItemCommand lastDialogueCommand = commands.LastOrDefault(c => c.Verb == CommandVerb.DIALOGUE); - if (commands.FindLastIndex(c => c.Verb == CommandVerb.TOGGLE_DIALOGUE && - !((BoolScriptParameter)c.Parameters[0]).Value) < commands.IndexOf(lastDialogueCommand)) - { - DialogueLine line = ((DialogueScriptParameter)lastDialogueCommand.Parameters[0]).Line; - SKPaint dialoguePaint = line.Speaker switch - { - Speaker.MONOLOGUE => DialogueScriptParameter.Paint01, - Speaker.INFO => DialogueScriptParameter.Paint04, - _ => DialogueScriptParameter.Paint00, - }; - if (!string.IsNullOrEmpty(line.Text)) + ScriptItemCommand previousCommand = null; + foreach (ScriptItemCommand command in commands) { - canvas.DrawBitmap(_project.DialogueBitmap, new SKRect(0, 24, 32, 36), new SKRect(0, 344, 256, 356)); - SKColor dialogueBoxColor = _project.DialogueBitmap.GetPixel(0, 28); - canvas.DrawRect(0, 356, 224, 384, new() { Color = dialogueBoxColor }); - canvas.DrawBitmap(_project.DialogueBitmap, new SKRect(0, 37, 32, 64), new SKRect(224, 356, 256, 384)); - canvas.DrawBitmap(_project.SpeakerBitmap, new SKRect(0, 16 * ((int)line.Speaker - 1), 64, 16 * ((int)line.Speaker)), - new SKRect(0, 332, 64, 348)); - - DrawText(line.Text, canvas, dialoguePaint, _project); - } - } - } - - canvas.Flush(); - - _preview.Items.Add(new SKGuiImage(previewBitmap)); - } + if (previousCommand?.Verb == CommandVerb.DIALOGUE) + { + SpriteExitScriptParameter spriteExitMoveParam = (SpriteExitScriptParameter)previousCommand?.Parameters[3]; // exits/moves happen _after_ dialogue is advanced, so we check these at this point + if (spriteExitMoveParam.ExitTransition != SpriteExitScriptParameter.SpriteExitTransition.NO_EXIT) + { + CharacterItem prevCharacter = (CharacterItem)_project.Items.First(i => i.Type == ItemDescription.ItemType.Character && + i.Name == $"CHR_{_project.Characters[(int)((DialogueScriptParameter)previousCommand.Parameters[0]).Line.Speaker].Name}"); + SpriteScriptParameter previousSpriteParam = (SpriteScriptParameter)previousCommand.Parameters[1]; + short layer = ((ShortScriptParameter)previousCommand.Parameters[9]).Value; + switch (spriteExitMoveParam.ExitTransition) + { + case SpriteExitScriptParameter.SpriteExitTransition.SLIDE_LEFT_TO_LEFT_FADE_OUT: + case SpriteExitScriptParameter.SpriteExitTransition.SLIDE_LEFT_TO_RIGHT_FADE_OUT: + case SpriteExitScriptParameter.SpriteExitTransition.SLIDE_FROM_CENTER_TO_LEFT_FADE_OUT: + case SpriteExitScriptParameter.SpriteExitTransition.SLIDE_FROM_CENTER_TO_RIGHT_FADE_OUT: + case SpriteExitScriptParameter.SpriteExitTransition.FADE_OUT_CENTER: + case SpriteExitScriptParameter.SpriteExitTransition.FADE_OUT_LEFT: + if (sprites.ContainsKey(prevCharacter) && previousSprites.ContainsKey(prevCharacter) && ((SpriteScriptParameter)previousCommand.Parameters[1]).Sprite?.Sprite?.Character == prevCharacter.MessageInfo.Character) + { + sprites.Remove(prevCharacter); + previousSprites.Remove(prevCharacter); + } + break; - public static void DrawText(string text, SKCanvas canvas, SKPaint color, Project project, int x = 10, int y = 352, bool formatting = true) - { - int currentX = x; - int currentY = y; + case SpriteExitScriptParameter.SpriteExitTransition.SLIDE_CENTER_TO_LEFT_AND_STAY: + case SpriteExitScriptParameter.SpriteExitTransition.SLIDE_RIGHT_TO_LEFT_AND_STAY: + sprites[prevCharacter] = new() { Sprite = previousSpriteParam.Sprite, Positioning = new() { Position = SpritePositioning.SpritePosition.LEFT, Layer = layer } }; + break; - for (int i = 0; i < text.Length; i++) - { - // handle newlines - if (text[i] == '\n') - { - currentX = 10; - currentY += 14; - continue; - } + case SpriteExitScriptParameter.SpriteExitTransition.SLIDE_CENTER_TO_RIGHT_AND_STAY: + case SpriteExitScriptParameter.SpriteExitTransition.SLIDE_LEFT_TO_RIGHT_AND_STAY: + sprites[prevCharacter] = new() { Sprite = previousSpriteParam.Sprite, Positioning = new() { Position = SpritePositioning.SpritePosition.RIGHT, Layer = layer } }; + break; + } + } + } + if (command.Verb == CommandVerb.DIALOGUE) + { + SpriteScriptParameter spriteParam = (SpriteScriptParameter)command.Parameters[1]; + SKPaint spritePaint = PaletteEffectScriptParameter.IdentityPaint; + if (commands.IndexOf(palCommand) > commands.IndexOf(command)) + { + spritePaint = ((PaletteEffectScriptParameter)palCommand.Parameters[0]).Effect switch + { + PaletteEffectScriptParameter.PaletteEffect.INVERTED => PaletteEffectScriptParameter.InvertedPaint, + PaletteEffectScriptParameter.PaletteEffect.GRAYSCALE => PaletteEffectScriptParameter.GrayscalePaint, + PaletteEffectScriptParameter.PaletteEffect.SEPIA => PaletteEffectScriptParameter.SepiaPaint, + PaletteEffectScriptParameter.PaletteEffect.DIMMED => PaletteEffectScriptParameter.DimmedPaint, + _ => PaletteEffectScriptParameter.IdentityPaint, + }; + } + if (spriteParam.Sprite is not null) + { + CharacterItem character; + try + { + character = (CharacterItem)_project.Items.First(i => i.Type == ItemDescription.ItemType.Character && + i.DisplayName == $"CHR_{_project.Characters[(int)((DialogueScriptParameter)command.Parameters[0]).Line.Speaker].Name}"); + } + catch (InvalidOperationException) + { + _log.LogWarning($"Unable to determine speaking character in DIALOGUE command in {_script.DisplayName}."); + using Stream noPreviewStream = Assembly.GetCallingAssembly().GetManifestResourceStream("SerialLoops.Graphics.ScriptPreviewError.png"); + canvas.DrawImage(SKImage.FromEncodedData(noPreviewStream), new SKPoint(0, 0)); + canvas.Flush(); + _preview.Items.Add(new SKGuiImage(previewBitmap)); + return; + } + SpriteEntranceScriptParameter spriteEntranceParam = (SpriteEntranceScriptParameter)command.Parameters[2]; + short layer = ((ShortScriptParameter)command.Parameters[9]).Value; - // handle operators - if (formatting) - { - if (i < text.Length - 2 && Regex.IsMatch(text[i..(i + 2)], @"\$\d")) - { - if (i < text.Length - 3 && Regex.IsMatch(text[i..(i + 3)], @"\$\d{2}")) + if (!sprites.ContainsKey(character) && spriteEntranceParam.EntranceTransition != SpriteEntranceScriptParameter.SpriteEntranceTransition.NO_TRANSITION) + { + sprites.Add(character, new()); + previousSprites.Add(character, new()); + } + if (sprites.ContainsKey(character)) + { + previousSprites[character] = sprites[character]; + } + if (spriteEntranceParam.EntranceTransition != SpriteEntranceScriptParameter.SpriteEntranceTransition.NO_TRANSITION) + { + switch (spriteEntranceParam.EntranceTransition) + { + case SpriteEntranceScriptParameter.SpriteEntranceTransition.FADE_TO_CENTER: + case SpriteEntranceScriptParameter.SpriteEntranceTransition.SLIDE_LEFT_TO_CENTER: + case SpriteEntranceScriptParameter.SpriteEntranceTransition.SLIDE_RIGHT_TO_CENTER: + sprites[character] = new() { Sprite = spriteParam.Sprite, Positioning = new() { Position = SpritePositioning.SpritePosition.CENTER, Layer = layer }, PalEffect = spritePaint }; + break; + + case SpriteEntranceScriptParameter.SpriteEntranceTransition.FADE_IN_LEFT: + case SpriteEntranceScriptParameter.SpriteEntranceTransition.PEEK_RIGHT_TO_LEFT: + case SpriteEntranceScriptParameter.SpriteEntranceTransition.SLIDE_RIGHT_TO_LEFT: + case SpriteEntranceScriptParameter.SpriteEntranceTransition.SLIDE_RIGHT_TO_LEFT_FAST: + case SpriteEntranceScriptParameter.SpriteEntranceTransition.SLIDE_RIGHT_TO_LEFT_SLOW: + sprites[character] = new() { Sprite = spriteParam.Sprite, Positioning = new() { Position = SpritePositioning.SpritePosition.LEFT, Layer = layer }, PalEffect = spritePaint }; + break; + + case SpriteEntranceScriptParameter.SpriteEntranceTransition.SLIDE_LEFT_TO_RIGHT: + case SpriteEntranceScriptParameter.SpriteEntranceTransition.SLIDE_LEFT_TO_RIGHT_FAST: + case SpriteEntranceScriptParameter.SpriteEntranceTransition.SLIDE_LEFT_TO_RIGHT_SLOW: + sprites[character] = new() { Sprite = spriteParam.Sprite, Positioning = new() { Position = SpritePositioning.SpritePosition.RIGHT, Layer = layer }, PalEffect = spritePaint }; + break; + } + } + else if (sprites.ContainsKey(character)) + { + sprites[character] = new() { Sprite = spriteParam.Sprite, Positioning = sprites[character].Positioning, PalEffect = spritePaint }; + } + } + } + else if (command.Verb == CommandVerb.INVEST_START) { - i++; + sprites.Clear(); + previousSprites.Clear(); } - i++; - continue; + previousCommand = command; } - else if (i < text.Length - 3 && Regex.IsMatch(text[i..(i + 3)], @"#W\d")) + + foreach (PositionedSprite sprite in sprites.Values.OrderBy(p => p.Positioning.Layer)) { - if (i < text.Length - 4 && Regex.IsMatch(text[i..(i + 4)], @"#W\d{2}")) - { - i++; - } - i += 2; - continue; + SKBitmap spriteBitmap = sprite.Sprite.GetClosedMouthAnimation(_project)[0].Frame; + canvas.DrawBitmap(spriteBitmap, sprite.Positioning.GetSpritePosition(spriteBitmap), sprite.PalEffect); } - else if (i < text.Length - 4 && Regex.IsMatch(text[i..(i + 4)], @"#P\d{2}")) + + // Draw dialogue + ScriptItemCommand lastDialogueCommand = commands.LastOrDefault(c => c.Verb == CommandVerb.DIALOGUE); + if (commands.FindLastIndex(c => c.Verb == CommandVerb.TOGGLE_DIALOGUE && + !((BoolScriptParameter)c.Parameters[0]).Value) < commands.IndexOf(lastDialogueCommand)) { - color = int.Parse(Regex.Match(text[i..(i + 4)], @"#P(?\d{2})").Groups["id"].Value) switch + DialogueLine line = ((DialogueScriptParameter)lastDialogueCommand.Parameters[0]).Line; + SKPaint dialoguePaint = line.Speaker switch { - 1 => DialogueScriptParameter.Paint01, - 2 => DialogueScriptParameter.Paint02, - 3 => DialogueScriptParameter.Paint03, - 4 => DialogueScriptParameter.Paint04, - 5 => DialogueScriptParameter.Paint05, - 6 => DialogueScriptParameter.Paint06, - 7 => DialogueScriptParameter.Paint07, + Speaker.MONOLOGUE => DialogueScriptParameter.Paint01, + Speaker.INFO => DialogueScriptParameter.Paint04, _ => DialogueScriptParameter.Paint00, }; - i += 3; - continue; - } - else if (i < text.Length - 3 && text[i..(i + 3)] == "#DP") - { - i += 3; - } - else if (i < text.Length - 6 && Regex.IsMatch(text[i..(i + 6)], @"#SE\d{3}")) - { - i += 6; - } - else if (i < text.Length - 4 && text[i..(i + 4)] == "#SK0") - { - i += 4; - } - else if (i < text.Length - 3 && text[i..(i + 3)] == "#sk") - { - i += 3; + if (!string.IsNullOrEmpty(line.Text)) + { + canvas.DrawBitmap(_project.DialogueBitmap, new SKRect(0, 24, 32, 36), new SKRect(0, 344, 256, 356)); + SKColor dialogueBoxColor = _project.DialogueBitmap.GetPixel(0, 28); + canvas.DrawRect(0, 356, 224, 384, new() { Color = dialogueBoxColor }); + canvas.DrawBitmap(_project.DialogueBitmap, new SKRect(0, 37, 32, 64), new SKRect(224, 356, 256, 384)); + canvas.DrawBitmap(_project.SpeakerBitmap, new SKRect(0, 16 * ((int)line.Speaker - 1), 64, 16 * ((int)line.Speaker)), + new SKRect(0, 332, 64, 348)); + + Shared.DrawText(line.Text, canvas, dialoguePaint, _project); + } } } - if (text[i] != ' ') // if it's a space, we just skip drawing - { - int charIndex = project.FontMap.CharMap.IndexOf(text[i]); - if ((charIndex + 1) * 16 <= project.FontBitmap.Height) - { - canvas.DrawBitmap(project.FontBitmap, new SKRect(0, charIndex * 16, 16, (charIndex + 1) * 16), - new SKRect(currentX, currentY, currentX + 16, currentY + 16), color); - } - } - FontReplacement replacement = project.FontReplacement.ReverseLookup(text[i]); - if (replacement is not null && project.LangCode != "ja") - { - currentX += replacement.Offset; - } - else - { - currentX += 14; - } + canvas.Flush(); + + _preview.Items.Add(new SKGuiImage(previewBitmap)); + } + catch (Exception ex) + { + _log.LogException("Failed to update preview!", ex); } } diff --git a/src/SerialLoops/Editors/VoicedLineEditor.cs b/src/SerialLoops/Editors/VoicedLineEditor.cs index ea74a79b..551de75c 100644 --- a/src/SerialLoops/Editors/VoicedLineEditor.cs +++ b/src/SerialLoops/Editors/VoicedLineEditor.cs @@ -220,7 +220,7 @@ private void UpdatePreview() { for (int i = 0; i <= 1; i++) { - ScriptEditor.DrawText( + Shared.DrawText( _subtitle, canvas, DialogueScriptParameter.Paint07, @@ -232,7 +232,7 @@ private void UpdatePreview() } } - ScriptEditor.DrawText( + Shared.DrawText( _subtitle, canvas, DialogueScriptParameter.Paint00, diff --git a/src/SerialLoops/MainForm.eto.cs b/src/SerialLoops/MainForm.eto.cs index 2ed489b7..3389aa94 100644 --- a/src/SerialLoops/MainForm.eto.cs +++ b/src/SerialLoops/MainForm.eto.cs @@ -908,7 +908,7 @@ private void Patch_Executed(object sender, EventArgs e) { LoopyProgressTracker tracker = new(); _ = new ProgressDialog( - () => Patch.CreatePatch(baseRomDialog.FileName, currentRom, outputPatchDialog.FileName), + () => Patch.CreatePatch(baseRomDialog.FileName, currentRom, outputPatchDialog.FileName, Log), () => MessageBox.Show("Patch Created!", "Success!", MessageBoxType.Information), tracker, "Creating Patch"); } diff --git a/src/SerialLoops/Utility/LoopyLogger.cs b/src/SerialLoops/Utility/LoopyLogger.cs index 263b3fff..d51acdb8 100644 --- a/src/SerialLoops/Utility/LoopyLogger.cs +++ b/src/SerialLoops/Utility/LoopyLogger.cs @@ -44,6 +44,12 @@ public void LogError(string message, bool lookForWarnings = false) } } + public void LogException(string message, Exception exception) + { + LogError(message); + LogWarning($"{exception.Message}\n\n{exception.StackTrace}"); + } + public void LogWarning(string message, bool lookForErrors = false) { if (_writer is not null && !string.IsNullOrEmpty(message)) diff --git a/src/SerialLoops/Utility/Shared.cs b/src/SerialLoops/Utility/Shared.cs index 20ad5dab..ccc6e1a0 100644 --- a/src/SerialLoops/Utility/Shared.cs +++ b/src/SerialLoops/Utility/Shared.cs @@ -1,9 +1,11 @@ using Eto.Forms; +using HaruhiChokuretsuLib.Font; using HaruhiChokuretsuLib.Util; using SerialLoops.Controls; using SerialLoops.Dialogs; using SerialLoops.Lib; using SerialLoops.Lib.Items; +using SerialLoops.Lib.Script.Parameters; using SerialLoops.Lib.Util; using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Gif; @@ -11,6 +13,7 @@ using SkiaSharp; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; namespace SerialLoops.Utility { @@ -36,7 +39,7 @@ public static void RenameItem(Project project, ItemExplorerPanel explorer, Edito } ((TreeGridView)explorer.Viewer.Control).ReloadData(); project.ItemNames[item.Name] = item.DisplayName; - project.Save(); + project.Save(log); } } public static void RenameItem(Project project, ItemExplorerPanel explorer, EditorTabsPanel tabs, ILogger log, string newName) @@ -62,7 +65,7 @@ public static void RenameItem(ItemDescription item, Project project, ItemExplore } ((TreeGridView)explorer.Viewer.Control).ReloadData(); project.ItemNames[item.Name] = item.DisplayName; - project.Save(); + project.Save(log); } } @@ -89,5 +92,96 @@ public static void SaveGif(this IEnumerable frames, string fileName, I gif.SaveAsGif(fileName); tracker.Finished++; } + + public static void DrawText(string text, SKCanvas canvas, SKPaint color, Project project, int x = 10, int y = 352, bool formatting = true) + { + int currentX = x; + int currentY = y; + + for (int i = 0; i < text.Length; i++) + { + // handle newlines + if (text[i] == '\n') + { + currentX = 10; + currentY += 14; + continue; + } + + // handle operators + if (formatting) + { + if (i < text.Length - 2 && Regex.IsMatch(text[i..(i + 2)], @"\$\d")) + { + if (i < text.Length - 3 && Regex.IsMatch(text[i..(i + 3)], @"\$\d{2}")) + { + i++; + } + i++; + continue; + } + else if (i < text.Length - 3 && Regex.IsMatch(text[i..(i + 3)], @"#W\d")) + { + if (i < text.Length - 4 && Regex.IsMatch(text[i..(i + 4)], @"#W\d{2}")) + { + i++; + } + i += 2; + continue; + } + else if (i < text.Length - 4 && Regex.IsMatch(text[i..(i + 4)], @"#P\d{2}")) + { + color = int.Parse(Regex.Match(text[i..(i + 4)], @"#P(?\d{2})").Groups["id"].Value) switch + { + 1 => DialogueScriptParameter.Paint01, + 2 => DialogueScriptParameter.Paint02, + 3 => DialogueScriptParameter.Paint03, + 4 => DialogueScriptParameter.Paint04, + 5 => DialogueScriptParameter.Paint05, + 6 => DialogueScriptParameter.Paint06, + 7 => DialogueScriptParameter.Paint07, + _ => DialogueScriptParameter.Paint00, + }; + i += 3; + continue; + } + else if (i < text.Length - 3 && text[i..(i + 3)] == "#DP") + { + i += 3; + } + else if (i < text.Length - 6 && Regex.IsMatch(text[i..(i + 6)], @"#SE\d{3}")) + { + i += 6; + } + else if (i < text.Length - 4 && text[i..(i + 4)] == "#SK0") + { + i += 4; + } + else if (i < text.Length - 3 && text[i..(i + 3)] == "#sk") + { + i += 3; + } + } + + if (text[i] != ' ') // if it's a space, we just skip drawing + { + int charIndex = project.FontMap.CharMap.IndexOf(text[i]); + if ((charIndex + 1) * 16 <= project.FontBitmap.Height) + { + canvas.DrawBitmap(project.FontBitmap, new SKRect(0, charIndex * 16, 16, (charIndex + 1) * 16), + new SKRect(currentX, currentY, currentX + 16, currentY + 16), color); + } + } + FontReplacement replacement = project.FontReplacement.ReverseLookup(text[i]); + if (replacement is not null && project.LangCode != "ja") + { + currentX += replacement.Offset; + } + else + { + currentX += 14; + } + } + } } } diff --git a/test/SerialLoops.Tests/CoreTests.cs b/test/SerialLoops.Tests/CoreTests.cs index 857b9eb6..b2c9c349 100644 --- a/test/SerialLoops.Tests/CoreTests.cs +++ b/test/SerialLoops.Tests/CoreTests.cs @@ -59,12 +59,12 @@ public void SetUp() Directory.CreateDirectory(Path.Combine(_project.BaseDirectory, vceDir)); Directory.CreateDirectory(Path.Combine(_project.IterativeDirectory, vceDir)); - IO.CopyFiles(_dataDir, Path.Combine(_project.BaseDirectory, archivesDir)); - IO.CopyFiles(_dataDir, Path.Combine(_project.IterativeDirectory, archivesDir)); - IO.CopyFiles(Path.Combine(_dataDir, "bgm"), Path.Combine(_project.BaseDirectory, bgmDir)); - IO.CopyFiles(Path.Combine(_dataDir, "bgm"), Path.Combine(_project.IterativeDirectory, bgmDir)); - IO.CopyFiles(Path.Combine(_dataDir, "vce"), Path.Combine(_project.BaseDirectory, vceDir)); - IO.CopyFiles(Path.Combine(_dataDir, "vce"), Path.Combine(_project.IterativeDirectory, vceDir)); + IO.CopyFiles(_dataDir, Path.Combine(_project.BaseDirectory, archivesDir), _log); + IO.CopyFiles(_dataDir, Path.Combine(_project.IterativeDirectory, archivesDir), _log); + IO.CopyFiles(Path.Combine(_dataDir, "bgm"), Path.Combine(_project.BaseDirectory, bgmDir), _log); + IO.CopyFiles(Path.Combine(_dataDir, "bgm"), Path.Combine(_project.IterativeDirectory, bgmDir), _log); + IO.CopyFiles(Path.Combine(_dataDir, "vce"), Path.Combine(_project.BaseDirectory, vceDir), _log); + IO.CopyFiles(Path.Combine(_dataDir, "vce"), Path.Combine(_project.IterativeDirectory, vceDir), _log); // Load the project archives _project.LoadArchives(_log, _progressTracker); diff --git a/test/SerialLoops.Tests/StartUpTests.cs b/test/SerialLoops.Tests/StartUpTests.cs index ef066529..e6520e9f 100644 --- a/test/SerialLoops.Tests/StartUpTests.cs +++ b/test/SerialLoops.Tests/StartUpTests.cs @@ -27,7 +27,7 @@ private async Task DownloadTestRom() HttpClient client = new(); File.WriteAllBytes(romPath, await client.GetByteArrayAsync("https://github.com/WiIIiam278/BCSDS/releases/download/1.0/bcsds.nds")); - Lib.IO.OpenRom(project, romPath, new ConsoleProgressTracker()); + Lib.IO.OpenRom(project, romPath, new ConsoleLogger(), new ConsoleProgressTracker()); return project; }