diff --git a/addons/GodotInk/Src/InkDock.cs b/addons/GodotInk/Src/InkDock.cs index 0988bc6..2a11fcb 100644 --- a/addons/GodotInk/Src/InkDock.cs +++ b/addons/GodotInk/Src/InkDock.cs @@ -22,6 +22,7 @@ public partial class InkDock : VBoxContainer private EditorFileDialog fileDialog = null!; + private string storyPath = ""; private InkStory? story; private bool storyStarted; @@ -74,7 +75,7 @@ private void UpdateTop() { bool hasStory = story != null; - storyNameLabel.Text = hasStory ? story!.ResourcePath : string.Empty; + storyNameLabel.Text = hasStory ? storyPath : string.Empty; startButton.Visible = hasStory && !storyStarted; stopButton.Visible = hasStory && storyStarted; @@ -88,7 +89,8 @@ private void LoadStory(string path) { try { - story = GD.Load(path); + storyPath = path; + story = ResourceLoader.Load(path, null, ResourceLoader.CacheMode.Ignore); story.Continued += ContinueStory; @@ -106,14 +108,20 @@ private void StartStory() if (story == null) return; storyStarted = true; - _ = story.ContinueMaximally(); + story.ContinueMaximally(); UpdateTop(); } private void StopStory() + { + StopStory(false); + } + + private void StopStory(bool setStoryToNull) { storyStarted = false; + try { story?.ResetState(); @@ -123,6 +131,9 @@ private void StopStory() story = null; } + if (setStoryToNull) + story = null; + ClearStory(true); } @@ -193,14 +204,14 @@ private void ClickChoice(int idx) AddToStory(new HSeparator()); if (story.CanContinue) - _ = story.ContinueMaximally(); + story.ContinueMaximally(); } private async void AddToStory(CanvasItem item) { storyText.AddChild(item); - _ = await ToSignal(GetTree(), "process_frame"); - _ = await ToSignal(GetTree(), "process_frame"); + await ToSignal(GetTree(), "process_frame"); + await ToSignal(GetTree(), "process_frame"); scroll.ScrollVertical = (int)scroll.GetVScrollBar().MaxValue; } @@ -216,13 +227,9 @@ private void RemoveAllChoices() storyChoices.RemoveChild(n); } - public void WhenInkResourceReimported(string resourcePath) + public void WhenInkResourceReimported() { - if (story?.ResourcePath == resourcePath) - { - story = null; - StopStory(); - } + StopStory(true); } } diff --git a/addons/GodotInk/Src/InkStoryImporter.cs b/addons/GodotInk/Src/InkStoryImporter.cs index 47052bc..5d4106e 100644 --- a/addons/GodotInk/Src/InkStoryImporter.cs +++ b/addons/GodotInk/Src/InkStoryImporter.cs @@ -3,9 +3,17 @@ #nullable enable using Godot; -using Godot.Collections; using Ink; +using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Text.Json; +using System.Text.RegularExpressions; + +using DependenciesCache = System.Collections.Generic.Dictionary; +using GArrayGictionary = Godot.Collections.Array; +using GArrayString = Godot.Collections.Array; +using Gictionary = Godot.Collections.Dictionary; namespace GodotInk; @@ -15,6 +23,8 @@ public partial class InkStoryImporter : EditorImportPlugin private const string OPT_MAIN_FILE = "is_main_file"; private const string OPT_COMPRESS = "compress"; + private static readonly Regex includeRegex = new(@"^\s*INCLUDE\s*(?.*)\s*$", RegexOptions.Multiline | RegexOptions.Compiled); + #pragma warning disable IDE0022 public override string _GetImporterName() => "ink"; @@ -32,24 +42,29 @@ public partial class InkStoryImporter : EditorImportPlugin public override int _GetImportOrder() => 0; - public override Array _GetImportOptions(string path, int presetIndex) => new() + public override GArrayGictionary _GetImportOptions(string path, int presetIndex) => new() { new() { { "name", OPT_MAIN_FILE }, { "default_value", false } }, new() { { "name", OPT_COMPRESS }, { "default_value", true } } }; - public override bool _GetOptionVisibility(string path, StringName optionName, Dictionary options) => true; + public override bool _GetOptionVisibility(string path, StringName optionName, Gictionary options) => true; #pragma warning restore IDE0022 - public override Error _Import(string sourceFile, string savePath, - Dictionary options, Array platformVariants, Array genFiles) + public override Error _Import(string sourceFile, string savePath, Gictionary options, GArrayString _, GArrayString __) { + UpdateCache(sourceFile, ExtractIncludes(sourceFile)); + string destFile = $"{savePath}.{_GetSaveExtension()}"; + Error returnValue = options[OPT_MAIN_FILE].AsBool() + ? ImportFromInk(sourceFile, destFile, options[OPT_COMPRESS].AsBool()) + : ResourceSaver.Save(new StubInkStory(), destFile); - if (!options[OPT_MAIN_FILE].AsBool()) - return ResourceSaver.Save(new StubInkStory(), destFile); + string[] additionalFiles = GetCache().Where(kvp => kvp.Value.Contains(sourceFile)).Select(kvp => kvp.Key).ToArray(); + foreach (var additionalFile in additionalFiles) + AppendImportExternalResource(additionalFile); - return ImportFromInk(sourceFile, destFile, options[OPT_COMPRESS].AsBool()); + return returnValue; } private static Error ImportFromInk(string sourceFile, string destFile, bool shouldCompress) @@ -64,7 +79,8 @@ private static Error ImportFromInk(string sourceFile, string destFile, bool shou sourceFilename = sourceFile, errorHandler = InkCompilerErrorHandler, fileHandler = new FileHandler( - Path.GetDirectoryName(file.GetPathAbsolute()) ?? ProjectSettings.GlobalizePath("res://")), + Path.GetDirectoryName(file.GetPathAbsolute()) ?? ProjectSettings.GlobalizePath("res://") + ), }); try @@ -81,6 +97,15 @@ private static Error ImportFromInk(string sourceFile, string destFile, bool shou } } + private static List ExtractIncludes(string sourceFile) + { + using Godot.FileAccess file = Godot.FileAccess.Open(sourceFile, Godot.FileAccess.ModeFlags.Read); + return includeRegex.Matches(file.GetAsText()) + .OfType() + .Select(match => sourceFile.GetBaseDir().PathJoin(match.Groups["Path"].Value)) + .ToList(); + } + private static void InkCompilerErrorHandler(string message, ErrorType errorType) { switch (errorType) @@ -94,6 +119,32 @@ private static void InkCompilerErrorHandler(string message, ErrorType errorType) } } + private const string CACHE_FILE = "user://ink_cache.json"; + + public static void UpdateCache(string sourceFile, List dependencies) + { + DependenciesCache cache = GetCache(); + cache[sourceFile] = dependencies.ToArray(); + cache = cache.Where(kvp => kvp.Value.Length > 0).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + + using Godot.FileAccess file = Godot.FileAccess.Open(CACHE_FILE, Godot.FileAccess.ModeFlags.Write); + file?.StoreString(JsonSerializer.Serialize(cache)); + } + + public static DependenciesCache GetCache() + { + using Godot.FileAccess file = Godot.FileAccess.Open(CACHE_FILE, Godot.FileAccess.ModeFlags.Read); + try + { + return JsonSerializer.Deserialize(file?.GetAsText() ?? "{}") + ?? new DependenciesCache(); + } + catch (JsonException) + { + return new DependenciesCache(); + } + } + private class FileHandler : IFileHandler { private readonly string rootDir; diff --git a/addons/GodotInk/plugin.gd b/addons/GodotInk/plugin.gd index 5d1883d..fac275e 100644 --- a/addons/GodotInk/plugin.gd +++ b/addons/GodotInk/plugin.gd @@ -32,7 +32,6 @@ func _when_resources_reimported(resources : PackedStringArray) -> void: return for resource in resources: - if resource.get_extension() != "ink": - continue - - _dock.call("WhenInkResourceReimported", resource) + if resource.get_extension() == "ink": + _dock.call("WhenInkResourceReimported") + return