You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
173 lines
5.8 KiB
C#
173 lines
5.8 KiB
C#
#if TOOLS
|
|
|
|
#nullable enable
|
|
|
|
using Godot;
|
|
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<string, string[]>;
|
|
using GArrayGictionary = Godot.Collections.Array<Godot.Collections.Dictionary>;
|
|
using GArrayString = Godot.Collections.Array<string>;
|
|
using Gictionary = Godot.Collections.Dictionary;
|
|
|
|
namespace GodotInk;
|
|
|
|
[Tool]
|
|
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*(?<Path>.*)\s*$", RegexOptions.Multiline | RegexOptions.Compiled);
|
|
|
|
#pragma warning disable IDE0022
|
|
public override string _GetImporterName() => "ink";
|
|
|
|
public override string _GetVisibleName() => "Ink story";
|
|
|
|
public override string[] _GetRecognizedExtensions() => new string[] { "ink" };
|
|
|
|
public override string _GetResourceType() => nameof(Resource);
|
|
|
|
public override string _GetSaveExtension() => "res";
|
|
|
|
public override float _GetPriority() => 1.0f;
|
|
|
|
public override int _GetPresetCount() => 0;
|
|
|
|
public override int _GetImportOrder() => 0;
|
|
|
|
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, Gictionary options) => true;
|
|
#pragma warning restore IDE0022
|
|
|
|
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);
|
|
|
|
string[] additionalFiles = GetCache().Where(kvp => kvp.Value.Contains(sourceFile)).Select(kvp => kvp.Key).ToArray();
|
|
foreach (var additionalFile in additionalFiles)
|
|
AppendImportExternalResource(additionalFile);
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
private static Error ImportFromInk(string sourceFile, string destFile, bool shouldCompress)
|
|
{
|
|
using Godot.FileAccess file = Godot.FileAccess.Open(sourceFile, Godot.FileAccess.ModeFlags.Read);
|
|
|
|
if (file == null)
|
|
return Godot.FileAccess.GetOpenError();
|
|
|
|
Compiler compiler = new(file.GetAsText(), new Compiler.Options
|
|
{
|
|
sourceFilename = sourceFile,
|
|
errorHandler = InkCompilerErrorHandler,
|
|
fileHandler = new FileHandler(
|
|
Path.GetDirectoryName(file.GetPathAbsolute()) ?? ProjectSettings.GlobalizePath("res://")
|
|
),
|
|
});
|
|
|
|
try
|
|
{
|
|
string storyContent = compiler.Compile().ToJson();
|
|
InkStory resource = InkStory.Create(storyContent);
|
|
ResourceSaver.SaverFlags flags = shouldCompress ? ResourceSaver.SaverFlags.Compress
|
|
: ResourceSaver.SaverFlags.None;
|
|
return ResourceSaver.Save(resource, destFile, flags);
|
|
}
|
|
catch (InvalidInkException)
|
|
{
|
|
return Error.CompilationFailed;
|
|
}
|
|
}
|
|
|
|
private static List<string> ExtractIncludes(string sourceFile)
|
|
{
|
|
using Godot.FileAccess file = Godot.FileAccess.Open(sourceFile, Godot.FileAccess.ModeFlags.Read);
|
|
return includeRegex.Matches(file.GetAsText())
|
|
.OfType<Match>()
|
|
.Select(match => sourceFile.GetBaseDir().PathJoin(match.Groups["Path"].Value.TrimEnd('\r')))
|
|
.ToList();
|
|
}
|
|
|
|
private static void InkCompilerErrorHandler(string message, ErrorType errorType)
|
|
{
|
|
switch (errorType)
|
|
{
|
|
case ErrorType.Warning:
|
|
GD.PushWarning(message);
|
|
break;
|
|
case ErrorType.Error:
|
|
GD.PushError(message);
|
|
throw new InvalidInkException();
|
|
case ErrorType.Author:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
private const string CACHE_FILE = "user://ink_cache.json";
|
|
|
|
public static void UpdateCache(string sourceFile, List<string> 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<DependenciesCache>(file?.GetAsText() ?? "{}")
|
|
?? new DependenciesCache();
|
|
}
|
|
catch (JsonException)
|
|
{
|
|
return new DependenciesCache();
|
|
}
|
|
}
|
|
|
|
private class FileHandler : IFileHandler
|
|
{
|
|
private readonly string rootDir;
|
|
|
|
public FileHandler(string rootDir)
|
|
{
|
|
this.rootDir = rootDir;
|
|
}
|
|
|
|
public string ResolveInkFilename(string includeName)
|
|
{
|
|
return Path.Combine(rootDir, includeName);
|
|
}
|
|
|
|
public string LoadInkFileContents(string fullFilename)
|
|
{
|
|
return File.ReadAllText(fullFilename);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|