diff --git a/locale/ui_en.properties b/locale/ui_en.properties index 61e3962..0e624d4 100644 --- a/locale/ui_en.properties +++ b/locale/ui_en.properties @@ -80,6 +80,7 @@ tools/scenario_editor/no_backgrounds = No backgrounds tools/scenario_editor/status_modified = Modified (unsaved) tools/scenario_editor/status_saved = Saved tools/scenario_editor/save_error = Save error: {0} +tools/scenario_editor/readonly_zip = File is provided by a zip mod and is read-only tools/scenario_editor/unsaved_switch_text = You have unsaved changes. Switch file without saving? tools/unsaved_title = Unsaved changes diff --git a/locale/ui_ru.properties b/locale/ui_ru.properties index 8f2c406..5f6cd52 100644 --- a/locale/ui_ru.properties +++ b/locale/ui_ru.properties @@ -81,6 +81,7 @@ tools/scenario_editor/no_backgrounds = Нет фонов tools/scenario_editor/status_modified = Изменено (не сохранено) tools/scenario_editor/status_saved = Сохранено tools/scenario_editor/save_error = Ошибка сохранения: {0} +tools/scenario_editor/readonly_zip = Файл из zip-мода доступен только для чтения tools/scenario_editor/unsaved_switch_text = У вас есть несохранённые изменения. Сменить файл без сохранения? tools/unsaved_title = Есть изменения diff --git a/src/InteractiveEditor/InteractiveEditorWindow.cs b/src/InteractiveEditor/InteractiveEditorWindow.cs index 61bcc3f..362e935 100644 --- a/src/InteractiveEditor/InteractiveEditorWindow.cs +++ b/src/InteractiveEditor/InteractiveEditorWindow.cs @@ -55,7 +55,6 @@ namespace Cthangover.Tools.InteractiveEditor public InteractiveEditorWindow() : base("tools/interactive_editor/title") { - Title = "Interactive Editor"; Size = new Vector2I(1280, 820); var outer = CreateFillContainer(); diff --git a/src/LightEditor/LightEditorWindow.cs b/src/LightEditor/LightEditorWindow.cs index 5742956..c37131d 100644 --- a/src/LightEditor/LightEditorWindow.cs +++ b/src/LightEditor/LightEditorWindow.cs @@ -1,7 +1,9 @@ +using System; using System.Collections.Generic; using System.Text.Json; using Cthangover.Core.Factories.Impls; using Cthangover.Core.UI.Lights; +using Cthangover.Core.Utils; using Cthangover.Tools.Services; using Godot; @@ -366,8 +368,9 @@ namespace Cthangover.Core.UI.Tool.LightEditor selectedIndex = -1; RefreshAll(); } - catch + catch (Exception ex) { + GameLogger.Log("LIGHT_EDITOR", $"Import JSON failed: {ex.Message}", LogLevel.Error); } } } diff --git a/src/ScenarioEditor/ScenarioEditTabs.cs b/src/ScenarioEditor/ScenarioEditTabs.cs index 2c976a5..82dd9a0 100644 --- a/src/ScenarioEditor/ScenarioEditTabs.cs +++ b/src/ScenarioEditor/ScenarioEditTabs.cs @@ -10,7 +10,9 @@ namespace Cthangover.Core.UI.Tool.ScenarioEditor { public sealed class TabData { - public string FilePath; + public string Key; + public string ModId; + public string RelativePath; public string FileName; public string SavedText; public bool IsDirty; @@ -31,8 +33,12 @@ namespace Cthangover.Core.UI.Tool.ScenarioEditor public TextEdit CodeEditor => _textEdit; public string CurrentText => _textEdit.Text; - public string CurrentAbsolutePath => - _activeTabIndex >= 0 && _activeTabIndex < _tabs.Count ? _tabs[_activeTabIndex].FilePath : null; + public string CurrentKey => + _activeTabIndex >= 0 && _activeTabIndex < _tabs.Count ? _tabs[_activeTabIndex].Key : null; + public string CurrentModId => + _activeTabIndex >= 0 && _activeTabIndex < _tabs.Count ? _tabs[_activeTabIndex].ModId : null; + public string CurrentRelativePath => + _activeTabIndex >= 0 && _activeTabIndex < _tabs.Count ? _tabs[_activeTabIndex].RelativePath : null; public bool HasActiveFile => _activeTabIndex >= 0 && _activeTabIndex < _tabs.Count; public bool IsCurrentDirty => _activeTabIndex >= 0 && _activeTabIndex < _tabs.Count && _tabs[_activeTabIndex].IsDirty; @@ -73,24 +79,35 @@ namespace Cthangover.Core.UI.Tool.ScenarioEditor AddChild(_graphView); } - public void OpenFile(string absolutePath) + public void OpenFile(string key) { for (int i = 0; i < _tabs.Count; i++) { - if (_tabs[i].FilePath == absolutePath) + if (_tabs[i].Key == key) { SwitchToTextTab(i); return; } } + if (!ScenarioFileService.TryParseKey(key, out var modId, out var relativePath)) + return; + try { - var text = ScenarioFileService.ReadAllText(absolutePath); + var text = ScenarioFileService.ReadAllText(modId, relativePath); + if (text == null) + { + GameLogger.Log("SCENARIO_EDITOR", $"Failed to read scenario '{key}'", LogLevel.Error); + return; + } + var tab = new TabData { - FilePath = absolutePath, - FileName = Path.GetFileNameWithoutExtension(absolutePath), + Key = key, + ModId = modId, + RelativePath = relativePath, + FileName = Path.GetFileNameWithoutExtension(relativePath), SavedText = text, IsDirty = false }; diff --git a/src/ScenarioEditor/ScenarioEditorWindow.cs b/src/ScenarioEditor/ScenarioEditorWindow.cs index d31436d..c5e1ec8 100644 --- a/src/ScenarioEditor/ScenarioEditorWindow.cs +++ b/src/ScenarioEditor/ScenarioEditorWindow.cs @@ -180,8 +180,8 @@ namespace Cthangover.Core.UI.Tool.ScenarioEditor private void OnTextTabSelected(string text) { - _filePathLabel.Text = _editTabs.CurrentAbsolutePath != null - ? ScenarioFileService.GetRelativePath(_editTabs.CurrentAbsolutePath) : ""; + _filePathLabel.Text = _editTabs.CurrentKey != null + ? ScenarioFileService.GetDisplayPath(_editTabs.CurrentModId, _editTabs.CurrentRelativePath) : ""; _saveBtn.Disabled = false; _openFileBtn.Disabled = false; _playBtn.Disabled = false; @@ -221,17 +221,23 @@ namespace Cthangover.Core.UI.Tool.ScenarioEditor if (!_editTabs.HasActiveFile) return; - var path = _editTabs.CurrentAbsolutePath; + var modId = _editTabs.CurrentModId; + var relativePath = _editTabs.CurrentRelativePath; var text = _editTabs.CurrentText; try { - ScenarioFileService.WriteAllText(path, text); + if (!ScenarioFileService.WriteAllText(modId, relativePath, text)) + { + _statusLabel.Text = TranslationServer.Translate("tools/scenario_editor/readonly_zip"); + return; + } + _editTabs.SaveCurrentFile(); _statusLabel.Text = TranslationServer.Translate("tools/scenario_editor/status_saved"); _infoPanel.Update(text); _thumbPanel.Load(text); _editTabs.RefreshGraph(); - GameLogger.Log("SCENARIO_EDITOR", $"Saved: {path}"); + GameLogger.Log("SCENARIO_EDITOR", $"Saved: {modId}/{relativePath}"); } catch (Exception ex) { @@ -243,10 +249,16 @@ namespace Cthangover.Core.UI.Tool.ScenarioEditor private void OnOpenFilePressed() { - var path = _editTabs.CurrentAbsolutePath; - if (path == null) + if (_editTabs.CurrentKey == null) return; + var path = ScenarioFileService.GetAbsolutePath(_editTabs.CurrentModId, _editTabs.CurrentRelativePath); + if (string.IsNullOrEmpty(path)) + { + _statusLabel.Text = TranslationServer.Translate("tools/scenario_editor/readonly_zip"); + return; + } + GD.Print($"[ScenarioEditor] Opening: {path}"); var err = OS.ShellOpen(path); if (err != Error.Ok) @@ -267,10 +279,21 @@ namespace Cthangover.Core.UI.Tool.ScenarioEditor return; } - var path = _editTabs.CurrentAbsolutePath; + var modId = _editTabs.CurrentModId; + var relativePath = _editTabs.CurrentRelativePath; try { - ScenarioFileService.WriteAllText(path, text); + if (ScenarioFileService.WriteAllText(modId, relativePath, text)) + { + _editTabs.SaveCurrentFile(); + } + else + { + _statusLabel.Text = TranslationServer.Translate("tools/scenario_editor/readonly_zip"); + GameLogger.Log("SCENARIO_EDITOR", + $"Test play: '{modId}/{relativePath}' is read-only, playing from the editor buffer", + LogLevel.Warning); + } } catch (Exception ex) { @@ -279,8 +302,6 @@ namespace Cthangover.Core.UI.Tool.ScenarioEditor return; } - _editTabs.SaveCurrentFile(); - _editLayout.Visible = false; _playLayout.Visible = true; diff --git a/src/ScenarioEditor/ScenarioFileTreePanel.cs b/src/ScenarioEditor/ScenarioFileTreePanel.cs index dea93e1..b36d6ff 100644 --- a/src/ScenarioEditor/ScenarioFileTreePanel.cs +++ b/src/ScenarioEditor/ScenarioFileTreePanel.cs @@ -8,8 +8,8 @@ namespace Cthangover.Core.UI.Tool.ScenarioEditor public partial class ScenarioFileTreePanel : VBoxContainer { private Tree _fileTree; - private readonly Dictionary _fileItemToPath = new(); - private readonly Dictionary _pathToTreeItem = new(); + private readonly Dictionary _fileItemToKey = new(); + private readonly Dictionary _keyToTreeItem = new(); private bool _isProgrammaticSelection; public event Action FileSelected; @@ -25,7 +25,8 @@ namespace Cthangover.Core.UI.Tool.ScenarioEditor private void Populate() { _fileTree.Clear(); - _fileItemToPath.Clear(); + _fileItemToKey.Clear(); + _keyToTreeItem.Clear(); var root = _fileTree.CreateItem(); var mods = ScenarioFileService.ScanScenarioFiles(); @@ -56,8 +57,8 @@ namespace Cthangover.Core.UI.Tool.ScenarioEditor var fileItem = _fileTree.CreateItem(parentItem); fileItem.SetText(0, file.Name); fileItem.SetMetadata(0, Variant.From("scenario")); - _fileItemToPath[GetItemKey(fileItem)] = file.AbsolutePath; - _pathToTreeItem[file.AbsolutePath] = fileItem; + _fileItemToKey[GetItemKey(fileItem)] = file.Key; + _keyToTreeItem[file.Key] = fileItem; } } } @@ -68,16 +69,16 @@ namespace Cthangover.Core.UI.Tool.ScenarioEditor return item.GetInstanceId().ToString(); } - public void SelectFile(string absolutePath) + public void SelectFile(string key) { - if (!_pathToTreeItem.TryGetValue(absolutePath, out var item)) + if (!_keyToTreeItem.TryGetValue(key, out var item)) return; _isProgrammaticSelection = true; item.Select(0); _isProgrammaticSelection = false; - FileSelected?.Invoke(absolutePath); + FileSelected?.Invoke(key); } private void OnCellSelected() @@ -93,11 +94,11 @@ namespace Cthangover.Core.UI.Tool.ScenarioEditor if (meta.VariantType != Variant.Type.String || meta.AsString() != "scenario") return; - var key = GetItemKey(item); - if (!_fileItemToPath.TryGetValue(key, out var filePath)) + var itemKey = GetItemKey(item); + if (!_fileItemToKey.TryGetValue(itemKey, out var fileKey)) return; - FileSelected?.Invoke(filePath); + FileSelected?.Invoke(fileKey); } } } diff --git a/src/ScenarioEditor/ScenarioGraphView.cs b/src/ScenarioEditor/ScenarioGraphView.cs index 472428a..8df0d16 100644 --- a/src/ScenarioEditor/ScenarioGraphView.cs +++ b/src/ScenarioEditor/ScenarioGraphView.cs @@ -190,7 +190,7 @@ namespace Cthangover.Core.UI.Tool.ScenarioEditor foreach (var sc in nodeData.Scenarios.OrderBy(s => s.Priority)) { - var url = Uri.EscapeDataString(sc.FilePath); + var url = Uri.EscapeDataString(sc.Key); sb.AppendLine($" [{sc.Priority}] [url={url}]{sc.Name}[/url]"); } diff --git a/src/Services/ScenarioFileService.cs b/src/Services/ScenarioFileService.cs index e1b4650..f0f5228 100644 --- a/src/Services/ScenarioFileService.cs +++ b/src/Services/ScenarioFileService.cs @@ -6,7 +6,6 @@ using Cthangover.Core.Scenarios; using Cthangover.Core.Factories.Impls; using Cthangover.Core.Utils; using Cthangover.Core.Mods; -using Godot; namespace Cthangover.Tools.Services { @@ -34,15 +33,24 @@ namespace Cthangover.Tools.Services public List BackgroundRefs { get; set; } = new(); } - /// Describes a single scenario file found during scanning. + /// + /// Describes a single scenario file found during scanning. The file is + /// addressed by its owning mod and mod-relative path, so both folder + /// mods and zip mods are covered; is the stable + /// identity used by the editor UI. + /// public sealed class ScenarioFileInfo { - /// Absolute path on disk. - public string AbsolutePath { get; set; } + /// Identifier of the mod that provides the file. + public string ModId { get; set; } + /// Mod-relative path, e.g. scenarios/start.scenario. + public string RelativePath { get; set; } /// Filename without extension. public string Name { get; set; } /// Directory path relative to the mod's scenarios/ folder. public string RelativeDir { get; set; } + /// Stable UI identity: modId:relativePath. + public string Key => ScenarioFileService.MakeKey(ModId, RelativePath); } /// A group of scenario files within the same subdirectory under scenarios/. @@ -65,67 +73,107 @@ namespace Cthangover.Tools.Services /// /// Static service for discovering, reading, writing, and analysing scenario files. - /// Scans all mods/*/scenarios/**/*.scenario files in the project, groups - /// them by mod and subdirectory, parses YAML-like metadata blocks, and extracts - /// referenced background/scene/locale/quest identifiers. Used by - /// for the file tree and info panel. + /// Scans every loaded mod's scenarios/ folder through the mod file + /// provider abstraction (), so folder and zip + /// mods are discovered identically. Writing is only possible for folder mods: + /// zip mods are read-only, and reports that instead + /// of touching the filesystem. Used by for + /// the file tree and info panel. /// public static class ScenarioFileService { + private const string ScenariosDir = "scenarios"; + private const string ScenarioExtension = ".scenario"; + private const string KeySeparator = ":"; + /// - /// Recursively scans res://mods/*/scenarios/ for .scenario files. - /// Groups results by mod ID and subdirectory. Returns an empty list if the - /// mods directory does not exist. + /// Recursively scans every loaded mod's scenarios/ folder for + /// .scenario files via . Groups + /// results by mod ID and subdirectory. Returns an empty list when no + /// mods are loaded or none provide scenarios. /// public static List ScanScenarioFiles() { var result = new List(); - var roots = ModRegistry.AllModsRoots; + var manager = ModManager.Instance; - foreach (var (modsPath, _) in roots) + foreach (var mod in manager.Mods.Values.OrderBy(m => m.Id, StringComparer.OrdinalIgnoreCase)) { - if (!Directory.Exists(modsPath)) + var files = new List(); + CollectScenarioFiles(manager, mod.Id, ScenariosDir, files); + if (files.Count == 0) continue; - foreach (var modDir in Directory.GetDirectories(modsPath).OrderBy(d => Path.GetFileName(d))) - { - var modId = Path.GetFileName(modDir); - var scenariosDir = Path.Combine(modDir, "scenarios"); - if (!Directory.Exists(scenariosDir)) - continue; - - var scenarioFiles = Directory.GetFiles(scenariosDir, "*.scenario", SearchOption.AllDirectories); - if (scenarioFiles.Length == 0) - continue; - - var groups = scenarioFiles - .GroupBy(f => - { - var relDir = Path.GetRelativePath(scenariosDir, Path.GetDirectoryName(f)); - return relDir == "." ? "" : relDir.Replace('\\', '/'); - }) - .OrderBy(g => g.Key) + var groups = files + .GroupBy(f => f.RelativeDir) + .OrderBy(g => g.Key, StringComparer.OrdinalIgnoreCase) .Select(g => new ScenarioGroup { DirectoryPath = g.Key, - Files = g.OrderBy(f => Path.GetFileNameWithoutExtension(f)) - .Select(f => new ScenarioFileInfo - { - AbsolutePath = f, - Name = Path.GetFileNameWithoutExtension(f), - RelativeDir = g.Key - }) - .ToList() + Files = g.OrderBy(f => f.Name, StringComparer.OrdinalIgnoreCase).ToList() }) .ToList(); - result.Add(new ModScenarioTree { ModId = modId, Groups = groups }); - } + result.Add(new ModScenarioTree { ModId = mod.Id, Groups = groups }); } return result; } + private static void CollectScenarioFiles(ModManager manager, string modId, string directory, List result) + { + foreach (var entry in manager.ListFiles(modId, directory)) + { + if (entry.EndsWith("/")) + { + CollectScenarioFiles(manager, modId, entry.TrimEnd('/'), result); + continue; + } + + if (!entry.EndsWith(ScenarioExtension, StringComparison.OrdinalIgnoreCase)) + continue; + + var path = entry.Replace('\\', '/'); + var lastSlash = path.LastIndexOf('/'); + var fileName = lastSlash >= 0 ? path.Substring(lastSlash + 1) : path; + var dirPath = lastSlash >= 0 ? path.Substring(0, lastSlash) : ""; + + var relativeDir = ""; + if (dirPath.StartsWith(ScenariosDir + "/", StringComparison.OrdinalIgnoreCase)) + relativeDir = dirPath.Substring(ScenariosDir.Length + 1); + else if (!dirPath.Equals(ScenariosDir, StringComparison.OrdinalIgnoreCase)) + relativeDir = dirPath; + + result.Add(new ScenarioFileInfo + { + ModId = modId, + RelativePath = path, + Name = Path.GetFileNameWithoutExtension(fileName), + RelativeDir = relativeDir + }); + } + } + + /// Builds the stable UI identity for a scenario file. + public static string MakeKey(string modId, string relativePath) => modId + KeySeparator + relativePath; + + /// Splits a key produced by back into mod ID and mod-relative path. + public static bool TryParseKey(string key, out string modId, out string relativePath) + { + modId = null; + relativePath = null; + if (string.IsNullOrEmpty(key)) + return false; + + var separator = key.IndexOf(KeySeparator, StringComparison.Ordinal); + if (separator <= 0 || separator >= key.Length - 1) + return false; + + modId = key.Substring(0, separator); + relativePath = key.Substring(separator + 1); + return true; + } + /// /// Parses the header block (before first --- line) in YAML-like /// key: value format to extract scene, priority, and condition. @@ -208,84 +256,95 @@ namespace Cthangover.Tools.Services return refs; } - /// Reads the entire file at as a UTF-8 string. - public static string ReadAllText(string filePath) + /// Reads a scenario file through its mod provider. Returns null when not found. + public static string ReadAllText(string modId, string relativePath) { - return File.ReadAllText(filePath); + return ModManager.Instance.ReadFileText(modId, relativePath); } - /// Writes to , overwriting existing content. - public static void WriteAllText(string filePath, string text) + /// + /// Writes a scenario file back to disk. Returns false when the + /// owning mod is not filesystem-backed (zip mods are read-only). + /// + public static bool WriteAllText(string modId, string relativePath, string text) { - File.WriteAllText(filePath, text); + var absolutePath = ModManager.Instance.GetFileSystemPath(modId, relativePath); + if (string.IsNullOrEmpty(absolutePath)) + return false; + + File.WriteAllText(absolutePath, text); + return true; } - /// Converts an absolute file path to a path relative to res://, using forward slashes. - public static string GetRelativePath(string absolutePath) + /// Absolute filesystem path of a scenario file, or null for zip-backed mods. + public static string GetAbsolutePath(string modId, string relativePath) { - var projectPath = GameEnvironment.IsRuntime ? ProjectSettings.GlobalizePath("res://") : ""; - return Path.GetRelativePath(projectPath, absolutePath).Replace('\\', '/'); + return ModManager.Instance.GetFileSystemPath(modId, relativePath); + } + + /// Project-style display path (mods/<modId>/<relativePath>). + public static string GetDisplayPath(string modId, string relativePath) + { + return "mods/" + modId + "/" + relativePath; } public static SceneGraphData BuildSceneGraph() { var data = new SceneGraphData(); - var sceneNodes = new Dictionary(System.StringComparer.OrdinalIgnoreCase); - var edgeSet = new HashSet(System.StringComparer.OrdinalIgnoreCase); + var sceneNodes = new Dictionary(StringComparer.OrdinalIgnoreCase); + var edgeSet = new HashSet(StringComparer.OrdinalIgnoreCase); - var allFiles = ScanAllScenarioFiles(); - - foreach (var file in allFiles) + foreach (var mod in ScanScenarioFiles()) { - string text; - try + foreach (var group in mod.Groups) { - text = File.ReadAllText(file.AbsolutePath); - } - catch - { - continue; - } - - var metadata = ParseMetadata(text); - if (string.IsNullOrEmpty(metadata.Scene) || metadata.Scene == "-") - continue; - - var sceneKey = metadata.Scene.ToLowerInvariant(); - - if (!sceneNodes.TryGetValue(sceneKey, out var node)) - { - node = new SceneGraphNode + foreach (var file in group.Files) { - SceneName = metadata.Scene, - ModId = file.ModId, - IsStartScene = string.Equals(metadata.Scene, "start_scene", System.StringComparison.OrdinalIgnoreCase) - }; - sceneNodes[sceneKey] = node; - } + var text = ReadAllText(file.ModId, file.RelativePath); + if (text == null) + continue; - node.ScenarioCount++; - node.Scenarios.Add(new ScenarioGraphLink - { - Name = file.Name, - Priority = int.TryParse(metadata.Priority, out var p) ? p : 0, - FilePath = file.AbsolutePath - }); + var metadata = ParseMetadata(text); + if (string.IsNullOrEmpty(metadata.Scene) || metadata.Scene == "-") + continue; - var refs = ExtractReferences(text); - foreach (var target in refs.SwitchTargets) - { - var srcKey = metadata.Scene.ToLowerInvariant(); - var dstKey = target.ToLowerInvariant(); - var edgeKey = srcKey + "|" + dstKey; - if (!edgeSet.Contains(edgeKey)) - { - edgeSet.Add(edgeKey); - data.Edges.Add(new SceneGraphEdge + var sceneKey = metadata.Scene.ToLowerInvariant(); + + if (!sceneNodes.TryGetValue(sceneKey, out var node)) { - FromScene = metadata.Scene, - ToScene = target + node = new SceneGraphNode + { + SceneName = metadata.Scene, + ModId = file.ModId, + IsStartScene = string.Equals(metadata.Scene, "start_scene", StringComparison.OrdinalIgnoreCase) + }; + sceneNodes[sceneKey] = node; + } + + node.ScenarioCount++; + node.Scenarios.Add(new ScenarioGraphLink + { + Name = file.Name, + Priority = int.TryParse(metadata.Priority, out var p) ? p : 0, + Key = file.Key }); + + var refs = ExtractReferences(text); + foreach (var target in refs.SwitchTargets) + { + var srcKey = metadata.Scene.ToLowerInvariant(); + var dstKey = target.ToLowerInvariant(); + var edgeKey = srcKey + "|" + dstKey; + if (!edgeSet.Contains(edgeKey)) + { + edgeSet.Add(edgeKey); + data.Edges.Add(new SceneGraphEdge + { + FromScene = metadata.Scene, + ToScene = target + }); + } + } } } } @@ -295,44 +354,5 @@ namespace Cthangover.Tools.Services return data; } - - private sealed class FlatScenarioFile - { - public string AbsolutePath; - public string Name; - public string ModId; - } - - private static List ScanAllScenarioFiles() - { - var result = new List(); - var roots = ModRegistry.AllModsRoots; - - foreach (var (modsPath, _) in roots) - { - if (!Directory.Exists(modsPath)) - continue; - - foreach (var modDir in Directory.GetDirectories(modsPath)) - { - var modId = Path.GetFileName(modDir); - var scenariosDir = Path.Combine(modDir, "scenarios"); - if (!Directory.Exists(scenariosDir)) - continue; - - foreach (var file in Directory.GetFiles(scenariosDir, "*.scenario", SearchOption.AllDirectories)) - { - result.Add(new FlatScenarioFile - { - AbsolutePath = file, - Name = Path.GetFileNameWithoutExtension(file), - ModId = modId - }); - } - } - } - - return result; - } } } diff --git a/src/Services/ScenarioGraphData.cs b/src/Services/ScenarioGraphData.cs index e7bf68a..34dec5f 100644 --- a/src/Services/ScenarioGraphData.cs +++ b/src/Services/ScenarioGraphData.cs @@ -6,7 +6,7 @@ namespace Cthangover.Tools.Services { public string Name { get; set; } public int Priority { get; set; } - public string FilePath { get; set; } + public string Key { get; set; } } public sealed class SceneGraphNode