Add more NPCs, update dialogue manager to 4.4 compatible version
This commit is contained in:
@@ -6,23 +6,25 @@ using System.Threading.Tasks;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace DialogueManagerRuntime;
|
||||
|
||||
public enum TranslationSource
|
||||
namespace DialogueManagerRuntime
|
||||
{
|
||||
public enum TranslationSource
|
||||
{
|
||||
None,
|
||||
Guess,
|
||||
CSV,
|
||||
PO
|
||||
}
|
||||
}
|
||||
|
||||
public partial class DialogueManager : Node
|
||||
{
|
||||
public partial class DialogueManager : RefCounted
|
||||
{
|
||||
public delegate void DialogueStartedEventHandler(Resource dialogueResource);
|
||||
public delegate void PassedTitleEventHandler(string title);
|
||||
public delegate void GotDialogueEventHandler(DialogueLine dialogueLine);
|
||||
public delegate void MutatedEventHandler(Dictionary mutation);
|
||||
public delegate void DialogueEndedEventHandler(Resource dialogueResource);
|
||||
|
||||
public static DialogueStartedEventHandler? DialogueStarted;
|
||||
public static PassedTitleEventHandler? PassedTitle;
|
||||
public static GotDialogueEventHandler? GotDialogue;
|
||||
public static MutatedEventHandler? Mutated;
|
||||
@@ -38,6 +40,7 @@ public partial class DialogueManager : Node
|
||||
if (instance == null)
|
||||
{
|
||||
instance = Engine.GetSingleton("DialogueManager");
|
||||
instance.Connect("bridge_dialogue_started", Callable.From((Resource dialogueResource) => DialogueStarted?.Invoke(dialogueResource)));
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
@@ -78,19 +81,18 @@ public partial class DialogueManager : Node
|
||||
}
|
||||
|
||||
|
||||
public void Prepare()
|
||||
public static void Prepare(GodotObject instance)
|
||||
{
|
||||
Instance.Connect("passed_title", Callable.From((string title) => PassedTitle?.Invoke(title)));
|
||||
Instance.Connect("got_dialogue", Callable.From((RefCounted line) => GotDialogue?.Invoke(new DialogueLine(line))));
|
||||
Instance.Connect("mutated", Callable.From((Dictionary mutation) => Mutated?.Invoke(mutation)));
|
||||
Instance.Connect("dialogue_ended", Callable.From((Resource dialogueResource) => DialogueEnded?.Invoke(dialogueResource)));
|
||||
instance.Connect("passed_title", Callable.From((string title) => PassedTitle?.Invoke(title)));
|
||||
instance.Connect("got_dialogue", Callable.From((RefCounted line) => GotDialogue?.Invoke(new DialogueLine(line))));
|
||||
instance.Connect("mutated", Callable.From((Dictionary mutation) => Mutated?.Invoke(mutation)));
|
||||
instance.Connect("dialogue_ended", Callable.From((Resource dialogueResource) => DialogueEnded?.Invoke(dialogueResource)));
|
||||
}
|
||||
|
||||
|
||||
public static async Task<GodotObject> GetSingleton()
|
||||
{
|
||||
if (instance != null)
|
||||
return instance;
|
||||
if (instance != null) return instance;
|
||||
|
||||
var tree = Engine.GetMainLoop();
|
||||
int x = 0;
|
||||
@@ -112,13 +114,20 @@ public partial class DialogueManager : Node
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static Resource CreateResourceFromText(string text)
|
||||
{
|
||||
return (Resource)Instance.Call("create_resource_from_text", text);
|
||||
}
|
||||
|
||||
public static async Task<DialogueLine?> GetNextDialogueLine(Resource dialogueResource, string key = "", Array<Variant>? extraGameStates = null)
|
||||
{
|
||||
Instance.Call("_bridge_get_next_dialogue_line", dialogueResource, key, extraGameStates ?? new Array<Variant>());
|
||||
var result = await Instance.ToSignal(Instance, "bridge_get_next_dialogue_line_completed");
|
||||
var instance = (Node)Instance.Call("_bridge_get_new_instance");
|
||||
Prepare(instance);
|
||||
instance.Call("_bridge_get_next_dialogue_line", dialogueResource, key, extraGameStates ?? new Array<Variant>());
|
||||
var result = await instance.ToSignal(instance, "bridge_get_next_dialogue_line_completed");
|
||||
instance.QueueFree();
|
||||
|
||||
if ((RefCounted)result[0] == null)
|
||||
return null;
|
||||
if ((RefCounted)result[0] == null) return null;
|
||||
|
||||
return new DialogueLine((RefCounted)result[0]);
|
||||
}
|
||||
@@ -159,19 +168,34 @@ public partial class DialogueManager : Node
|
||||
}
|
||||
|
||||
|
||||
public bool ThingHasMethod(GodotObject thing, string method)
|
||||
public bool ThingHasMethod(GodotObject thing, string method, Array<Variant> args)
|
||||
{
|
||||
MethodInfo? info = thing.GetType().GetMethod(method, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public);
|
||||
return info != null;
|
||||
var methodInfos = thing.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly);
|
||||
foreach (var methodInfo in methodInfos)
|
||||
{
|
||||
if (methodInfo.Name == method && args.Count == methodInfo.GetParameters().Length)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public async void ResolveThingMethod(GodotObject thing, string method, Array<Variant> args)
|
||||
{
|
||||
MethodInfo? info = thing.GetType().GetMethod(method, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public);
|
||||
MethodInfo? info = null;
|
||||
var methodInfos = thing.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly);
|
||||
foreach (var methodInfo in methodInfos)
|
||||
{
|
||||
if (methodInfo.Name == method && args.Count == methodInfo.GetParameters().Length)
|
||||
{
|
||||
info = methodInfo;
|
||||
}
|
||||
}
|
||||
|
||||
if (info == null)
|
||||
return;
|
||||
if (info == null) return;
|
||||
|
||||
#nullable disable
|
||||
// Convert the method args to something reflection can handle
|
||||
@@ -206,18 +230,15 @@ public partial class DialogueManager : Node
|
||||
|
||||
if (result is Task taskResult)
|
||||
{
|
||||
// await Tasks and handle result if it is a Task<T>
|
||||
await taskResult;
|
||||
var taskType = taskResult.GetType();
|
||||
if (taskType.IsGenericType && taskType.GetGenericTypeDefinition() == typeof(Task<>))
|
||||
try
|
||||
{
|
||||
var resultProperty = taskType.GetProperty("Result");
|
||||
var taskResultValue = resultProperty.GetValue(taskResult);
|
||||
EmitSignal(SignalName.Resolved, (Variant)taskResultValue);
|
||||
Variant value = (Variant)taskResult.GetType().GetProperty("Result").GetValue(taskResult);
|
||||
EmitSignal(SignalName.Resolved, value);
|
||||
}
|
||||
else
|
||||
catch (Exception err)
|
||||
{
|
||||
EmitSignal(SignalName.Resolved, null);
|
||||
EmitSignal(SignalName.Resolved);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -226,11 +247,11 @@ public partial class DialogueManager : Node
|
||||
}
|
||||
}
|
||||
#nullable enable
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public partial class DialogueLine : RefCounted
|
||||
{
|
||||
public partial class DialogueLine : RefCounted
|
||||
{
|
||||
private string id = "";
|
||||
public string Id
|
||||
{
|
||||
@@ -303,7 +324,17 @@ public partial class DialogueLine : RefCounted
|
||||
get => inline_mutations;
|
||||
}
|
||||
|
||||
private Array<DialogueLine> concurrent_lines = new Array<DialogueLine>();
|
||||
public Array<DialogueLine> ConcurrentLines
|
||||
{
|
||||
get => concurrent_lines;
|
||||
}
|
||||
|
||||
private Array<Variant> extra_game_states = new Array<Variant>();
|
||||
public Array<Variant> ExtraGameStates
|
||||
{
|
||||
get => extra_game_states;
|
||||
}
|
||||
|
||||
private Array<string> tags = new Array<string>();
|
||||
public Array<string> Tags
|
||||
@@ -324,6 +355,11 @@ public partial class DialogueLine : RefCounted
|
||||
time = (string)data.Get("time");
|
||||
tags = (Array<string>)data.Get("tags");
|
||||
|
||||
foreach (var concurrent_line_data in (Array<RefCounted>)data.Get("concurrent_lines"))
|
||||
{
|
||||
concurrent_lines.Add(new DialogueLine(concurrent_line_data));
|
||||
}
|
||||
|
||||
foreach (var response in (Array<RefCounted>)data.Get("responses"))
|
||||
{
|
||||
responses.Add(new DialogueResponse(response));
|
||||
@@ -356,11 +392,11 @@ public partial class DialogueLine : RefCounted
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public partial class DialogueResponse : RefCounted
|
||||
{
|
||||
public partial class DialogueResponse : RefCounted
|
||||
{
|
||||
private string next_id = "";
|
||||
public string NextId
|
||||
{
|
||||
@@ -421,5 +457,6 @@ public partial class DialogueResponse : RefCounted
|
||||
{
|
||||
return $"<DialogueResponse text=\"{text}\"";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://dx7nmsb8wv8ck
|
||||
uid://c4c5lsrwy3opj
|
||||
|
||||
1081
Zennysoft.Game.Ma/addons/dialogue_manager/compiler/compilation.gd
Normal file
1081
Zennysoft.Game.Ma/addons/dialogue_manager/compiler/compilation.gd
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
uid://dsgpnyqg6cprg
|
||||
@@ -0,0 +1,157 @@
|
||||
## A compiled line of dialogue.
|
||||
class_name DMCompiledLine extends RefCounted
|
||||
|
||||
|
||||
## The ID of the line
|
||||
var id: String
|
||||
## The translation key (or static line ID).
|
||||
var translation_key: String = ""
|
||||
## The type of line.
|
||||
var type: String = ""
|
||||
## The character name.
|
||||
var character: String = ""
|
||||
## Any interpolation expressions for the character name.
|
||||
var character_replacements: Array[Dictionary] = []
|
||||
## The text of the line.
|
||||
var text: String = ""
|
||||
## Any interpolation expressions for the text.
|
||||
var text_replacements: Array[Dictionary] = []
|
||||
## Any response siblings associated with this line.
|
||||
var responses: PackedStringArray = []
|
||||
## Any randomise or case siblings for this line.
|
||||
var siblings: Array[Dictionary] = []
|
||||
## Any lines said simultaneously.
|
||||
var concurrent_lines: PackedStringArray = []
|
||||
## Any tags on this line.
|
||||
var tags: PackedStringArray = []
|
||||
## The condition or mutation expression for this line.
|
||||
var expression: Dictionary = {}
|
||||
## The next sequential line to go to after this line.
|
||||
var next_id: String = ""
|
||||
## The next line to go to after this line if it is unknown and compile time.
|
||||
var next_id_expression: Array[Dictionary] = []
|
||||
## Whether this jump line should return after the jump target sequence has ended.
|
||||
var is_snippet: bool = false
|
||||
## The ID of the next sibling line.
|
||||
var next_sibling_id: String = ""
|
||||
## The ID after this line if it belongs to a block (eg. conditions).
|
||||
var next_id_after: String = ""
|
||||
## Any doc comments attached to this line.
|
||||
var notes: String = ""
|
||||
|
||||
|
||||
#region Hooks
|
||||
|
||||
|
||||
func _init(initial_id: String, initial_type: String) -> void:
|
||||
id = initial_id
|
||||
type = initial_type
|
||||
|
||||
|
||||
func _to_string() -> String:
|
||||
var s: Array = [
|
||||
"[%s]" % [type],
|
||||
"%s:" % [character] if character != "" else null,
|
||||
text if text != "" else null,
|
||||
expression if expression.size() > 0 else null,
|
||||
"[%s]" % [",".join(tags)] if tags.size() > 0 else null,
|
||||
str(siblings) if siblings.size() > 0 else null,
|
||||
str(responses) if responses.size() > 0 else null,
|
||||
"=> END" if "end" in next_id else "=> %s" % [next_id],
|
||||
"(~> %s)" % [next_sibling_id] if next_sibling_id != "" else null,
|
||||
"(==> %s)" % [next_id_after] if next_id_after != "" else null,
|
||||
].filter(func(item): return item != null)
|
||||
|
||||
return " ".join(s)
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
|
||||
## Express this line as a [Dictionary] that can be stored in a resource.
|
||||
func to_data() -> Dictionary:
|
||||
var d: Dictionary = {
|
||||
id = id,
|
||||
type = type,
|
||||
next_id = next_id
|
||||
}
|
||||
|
||||
if next_id_expression.size() > 0:
|
||||
d.next_id_expression = next_id_expression
|
||||
|
||||
match type:
|
||||
DMConstants.TYPE_CONDITION:
|
||||
d.condition = expression
|
||||
if not next_sibling_id.is_empty():
|
||||
d.next_sibling_id = next_sibling_id
|
||||
d.next_id_after = next_id_after
|
||||
|
||||
DMConstants.TYPE_WHILE:
|
||||
d.condition = expression
|
||||
d.next_id_after = next_id_after
|
||||
|
||||
DMConstants.TYPE_MATCH:
|
||||
d.condition = expression
|
||||
d.next_id_after = next_id_after
|
||||
d.cases = siblings
|
||||
|
||||
DMConstants.TYPE_MUTATION:
|
||||
d.mutation = expression
|
||||
|
||||
DMConstants.TYPE_GOTO:
|
||||
d.is_snippet = is_snippet
|
||||
d.next_id_after = next_id_after
|
||||
if not siblings.is_empty():
|
||||
d.siblings = siblings
|
||||
|
||||
DMConstants.TYPE_RANDOM:
|
||||
d.siblings = siblings
|
||||
|
||||
DMConstants.TYPE_RESPONSE:
|
||||
d.text = text
|
||||
|
||||
if not responses.is_empty():
|
||||
d.responses = responses
|
||||
|
||||
if translation_key != text:
|
||||
d.translation_key = translation_key
|
||||
if not expression.is_empty():
|
||||
d.condition = expression
|
||||
if not character.is_empty():
|
||||
d.character = character
|
||||
if not character_replacements.is_empty():
|
||||
d.character_replacements = character_replacements
|
||||
if not text_replacements.is_empty():
|
||||
d.text_replacements = text_replacements
|
||||
if not tags.is_empty():
|
||||
d.tags = tags
|
||||
if not notes.is_empty():
|
||||
d.notes = notes
|
||||
|
||||
DMConstants.TYPE_DIALOGUE:
|
||||
d.text = text
|
||||
|
||||
if translation_key != text:
|
||||
d.translation_key = translation_key
|
||||
|
||||
if not character.is_empty():
|
||||
d.character = character
|
||||
if not character_replacements.is_empty():
|
||||
d.character_replacements = character_replacements
|
||||
if not text_replacements.is_empty():
|
||||
d.text_replacements = text_replacements
|
||||
if not tags.is_empty():
|
||||
d.tags = tags
|
||||
if not notes.is_empty():
|
||||
d.notes = notes
|
||||
if not siblings.is_empty():
|
||||
d.siblings = siblings
|
||||
if not concurrent_lines.is_empty():
|
||||
d.concurrent_lines = concurrent_lines
|
||||
|
||||
return d
|
||||
|
||||
|
||||
#endregion
|
||||
@@ -0,0 +1 @@
|
||||
uid://dg8j5hudp4210
|
||||
@@ -0,0 +1,51 @@
|
||||
## A compiler of Dialogue Manager dialogue.
|
||||
class_name DMCompiler extends RefCounted
|
||||
|
||||
|
||||
## Compile a dialogue script.
|
||||
static func compile_string(text: String, path: String) -> DMCompilerResult:
|
||||
var compilation: DMCompilation = DMCompilation.new()
|
||||
compilation.compile(text, path)
|
||||
|
||||
var result: DMCompilerResult = DMCompilerResult.new()
|
||||
result.imported_paths = compilation.imported_paths
|
||||
result.using_states = compilation.using_states
|
||||
result.character_names = compilation.character_names
|
||||
result.titles = compilation.titles
|
||||
result.first_title = compilation.first_title
|
||||
result.errors = compilation.errors
|
||||
result.lines = compilation.data
|
||||
result.raw_text = text
|
||||
|
||||
return result
|
||||
|
||||
|
||||
## Get the line type of a string. The returned string will match one of the [code]TYPE_[/code] constants of [DMConstants].
|
||||
static func get_line_type(text: String) -> String:
|
||||
var compilation: DMCompilation = DMCompilation.new()
|
||||
return compilation.get_line_type(text)
|
||||
|
||||
|
||||
## Get the static line ID (eg. [code][ID:SOMETHING][/code]) of some text.
|
||||
static func get_static_line_id(text: String) -> String:
|
||||
var compilation: DMCompilation = DMCompilation.new()
|
||||
return compilation.extract_static_line_id(text)
|
||||
|
||||
|
||||
## Get the translatable part of a line.
|
||||
static func extract_translatable_string(text: String) -> String:
|
||||
var compilation: DMCompilation = DMCompilation.new()
|
||||
|
||||
var tree_line = DMTreeLine.new("")
|
||||
tree_line.text = text
|
||||
var line: DMCompiledLine = DMCompiledLine.new("", compilation.get_line_type(text))
|
||||
compilation.parse_character_and_dialogue(tree_line, line, [tree_line], 0, null)
|
||||
|
||||
return line.text
|
||||
|
||||
|
||||
## Get the known titles in a dialogue script.
|
||||
static func get_titles_in_text(text: String, path: String) -> Dictionary:
|
||||
var compilation: DMCompilation = DMCompilation.new()
|
||||
compilation.build_line_tree(compilation.inject_imported_files(text, path))
|
||||
return compilation.titles
|
||||
@@ -0,0 +1 @@
|
||||
uid://chtfdmr0cqtp4
|
||||
@@ -0,0 +1,49 @@
|
||||
## A collection of [RegEx] for use by the [DMCompiler].
|
||||
class_name DMCompilerRegEx extends RefCounted
|
||||
|
||||
|
||||
var IMPORT_REGEX: RegEx = RegEx.create_from_string("import \"(?<path>[^\"]+)\" as (?<prefix>[a-zA-Z_\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]+)")
|
||||
var USING_REGEX: RegEx = RegEx.create_from_string("^using (?<state>.*)$")
|
||||
var INDENT_REGEX: RegEx = RegEx.create_from_string("^\\t+")
|
||||
var VALID_TITLE_REGEX: RegEx = RegEx.create_from_string("^[a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]*$")
|
||||
var BEGINS_WITH_NUMBER_REGEX: RegEx = RegEx.create_from_string("^\\d")
|
||||
var CONDITION_REGEX: RegEx = RegEx.create_from_string("(if|elif|while|else if|match|when) (?<expression>.*)\\:?")
|
||||
var WRAPPED_CONDITION_REGEX: RegEx = RegEx.create_from_string("\\[if (?<expression>.*)\\]")
|
||||
var MUTATION_REGEX: RegEx = RegEx.create_from_string("(?<keyword>do|do!|set) (?<expression>.*)")
|
||||
var STATIC_LINE_ID_REGEX: RegEx = RegEx.create_from_string("\\[ID:(?<id>.*?)\\]")
|
||||
var WEIGHTED_RANDOM_SIBLINGS_REGEX: RegEx = RegEx.create_from_string("^\\%(?<weight>[\\d.]+)?( \\[if (?<condition>.+?)\\])? ")
|
||||
var GOTO_REGEX: RegEx = RegEx.create_from_string("=><? (?<goto>.*)")
|
||||
|
||||
var INLINE_RANDOM_REGEX: RegEx = RegEx.create_from_string("\\[\\[(?<options>.*?)\\]\\]")
|
||||
var INLINE_CONDITIONALS_REGEX: RegEx = RegEx.create_from_string("\\[if (?<condition>.+?)\\](?<body>.*?)\\[\\/if\\]")
|
||||
|
||||
var TAGS_REGEX: RegEx = RegEx.create_from_string("\\[#(?<tags>.*?)\\]")
|
||||
|
||||
var REPLACEMENTS_REGEX: RegEx = RegEx.create_from_string("{{(.*?)}}")
|
||||
|
||||
var ALPHA_NUMERIC: RegEx = RegEx.create_from_string("[^a-zA-Z0-9\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]+")
|
||||
|
||||
var TOKEN_DEFINITIONS: Dictionary = {
|
||||
DMConstants.TOKEN_FUNCTION: RegEx.create_from_string("^[a-zA-Z_\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]*\\("),
|
||||
DMConstants.TOKEN_DICTIONARY_REFERENCE: RegEx.create_from_string("^[a-zA-Z_\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]*\\["),
|
||||
DMConstants.TOKEN_PARENS_OPEN: RegEx.create_from_string("^\\("),
|
||||
DMConstants.TOKEN_PARENS_CLOSE: RegEx.create_from_string("^\\)"),
|
||||
DMConstants.TOKEN_BRACKET_OPEN: RegEx.create_from_string("^\\["),
|
||||
DMConstants.TOKEN_BRACKET_CLOSE: RegEx.create_from_string("^\\]"),
|
||||
DMConstants.TOKEN_BRACE_OPEN: RegEx.create_from_string("^\\{"),
|
||||
DMConstants.TOKEN_BRACE_CLOSE: RegEx.create_from_string("^\\}"),
|
||||
DMConstants.TOKEN_COLON: RegEx.create_from_string("^:"),
|
||||
DMConstants.TOKEN_COMPARISON: RegEx.create_from_string("^(==|<=|>=|<|>|!=|in )"),
|
||||
DMConstants.TOKEN_ASSIGNMENT: RegEx.create_from_string("^(\\+=|\\-=|\\*=|/=|=)"),
|
||||
DMConstants.TOKEN_NUMBER: RegEx.create_from_string("^\\-?\\d+(\\.\\d+)?"),
|
||||
DMConstants.TOKEN_OPERATOR: RegEx.create_from_string("^(\\+|\\-|\\*|/|%)"),
|
||||
DMConstants.TOKEN_COMMA: RegEx.create_from_string("^,"),
|
||||
DMConstants.TOKEN_DOT: RegEx.create_from_string("^\\."),
|
||||
DMConstants.TOKEN_STRING: RegEx.create_from_string("^&?(\".*?\"|\'.*?\')"),
|
||||
DMConstants.TOKEN_NOT: RegEx.create_from_string("^(not( |$)|!)"),
|
||||
DMConstants.TOKEN_AND_OR: RegEx.create_from_string("^(and|or|&&|\\|\\|)( |$)"),
|
||||
DMConstants.TOKEN_VARIABLE: RegEx.create_from_string("^[a-zA-Z_\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]*"),
|
||||
DMConstants.TOKEN_COMMENT: RegEx.create_from_string("^#.*"),
|
||||
DMConstants.TOKEN_CONDITION: RegEx.create_from_string("^(if|elif|else)"),
|
||||
DMConstants.TOKEN_BOOL: RegEx.create_from_string("^(true|false)")
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://d3tvcrnicjibp
|
||||
@@ -0,0 +1,27 @@
|
||||
## The result of using the [DMCompiler] to compile some dialogue.
|
||||
class_name DMCompilerResult extends RefCounted
|
||||
|
||||
|
||||
## Any paths that were imported into the compiled dialogue file.
|
||||
var imported_paths: PackedStringArray = []
|
||||
|
||||
## Any "using" directives.
|
||||
var using_states: PackedStringArray = []
|
||||
|
||||
## All titles in the file and the line they point to.
|
||||
var titles: Dictionary = {}
|
||||
|
||||
## The first title in the file.
|
||||
var first_title: String = ""
|
||||
|
||||
## All character names.
|
||||
var character_names: PackedStringArray = []
|
||||
|
||||
## Any compilation errors.
|
||||
var errors: Array[Dictionary] = []
|
||||
|
||||
## A map of all compiled lines.
|
||||
var lines: Dictionary = {}
|
||||
|
||||
## The raw dialogue text.
|
||||
var raw_text: String = ""
|
||||
@@ -0,0 +1 @@
|
||||
uid://dmk74tknimqvg
|
||||
@@ -0,0 +1,497 @@
|
||||
## A class for parsing a condition/mutation expression for use with the [DMCompiler].
|
||||
class_name DMExpressionParser extends RefCounted
|
||||
|
||||
|
||||
# Reference to the common [RegEx] that the parser needs.
|
||||
var regex: DMCompilerRegEx = DMCompilerRegEx.new()
|
||||
|
||||
|
||||
## Break a string down into an expression.
|
||||
func tokenise(text: String, line_type: String, index: int) -> Array:
|
||||
var tokens: Array[Dictionary] = []
|
||||
var limit: int = 0
|
||||
while text.strip_edges() != "" and limit < 1000:
|
||||
limit += 1
|
||||
var found = _find_match(text)
|
||||
if found.size() > 0:
|
||||
tokens.append({
|
||||
index = index,
|
||||
type = found.type,
|
||||
value = found.value
|
||||
})
|
||||
index += found.value.length()
|
||||
text = found.remaining_text
|
||||
elif text.begins_with(" "):
|
||||
index += 1
|
||||
text = text.substr(1)
|
||||
else:
|
||||
return _build_token_tree_error(DMConstants.ERR_INVALID_EXPRESSION, index)
|
||||
|
||||
return _build_token_tree(tokens, line_type, "")[0]
|
||||
|
||||
|
||||
## Extract any expressions from some text
|
||||
func extract_replacements(text: String, index: int) -> Array[Dictionary]:
|
||||
var founds: Array[RegExMatch] = regex.REPLACEMENTS_REGEX.search_all(text)
|
||||
|
||||
if founds == null or founds.size() == 0:
|
||||
return []
|
||||
|
||||
var replacements: Array[Dictionary] = []
|
||||
for found in founds:
|
||||
var replacement: Dictionary = {}
|
||||
var value_in_text: String = found.strings[0].substr(0, found.strings[0].length() - 2).substr(2)
|
||||
|
||||
# If there are closing curlie hard-up against the end of a {{...}} block then check for further
|
||||
# curlies just outside of the block.
|
||||
var text_suffix: String = text.substr(found.get_end(0))
|
||||
var expression_suffix: String = ""
|
||||
while text_suffix.begins_with("}"):
|
||||
expression_suffix += "}"
|
||||
text_suffix = text_suffix.substr(1)
|
||||
value_in_text += expression_suffix
|
||||
|
||||
var expression: Array = tokenise(value_in_text, DMConstants.TYPE_DIALOGUE, index + found.get_start(1))
|
||||
if expression.size() == 0:
|
||||
replacement = {
|
||||
index = index + found.get_start(1),
|
||||
error = DMConstants.ERR_INCOMPLETE_EXPRESSION
|
||||
}
|
||||
elif expression[0].type == DMConstants.TYPE_ERROR:
|
||||
replacement = {
|
||||
index = expression[0].index,
|
||||
error = expression[0].value
|
||||
}
|
||||
else:
|
||||
replacement = {
|
||||
value_in_text = "{{%s}}" % value_in_text,
|
||||
expression = expression
|
||||
}
|
||||
replacements.append(replacement)
|
||||
|
||||
return replacements
|
||||
|
||||
|
||||
#region Helpers
|
||||
|
||||
|
||||
# Create a token that represents an error.
|
||||
func _build_token_tree_error(error: int, index: int) -> Array:
|
||||
return [{ type = DMConstants.TOKEN_ERROR, value = error, index = index }]
|
||||
|
||||
|
||||
# Convert a list of tokens into an abstract syntax tree.
|
||||
func _build_token_tree(tokens: Array[Dictionary], line_type: String, expected_close_token: String) -> Array:
|
||||
var tree: Array[Dictionary] = []
|
||||
var limit = 0
|
||||
while tokens.size() > 0 and limit < 1000:
|
||||
limit += 1
|
||||
var token = tokens.pop_front()
|
||||
|
||||
var error = _check_next_token(token, tokens, line_type, expected_close_token)
|
||||
if error != OK:
|
||||
var error_token: Dictionary = tokens[1] if tokens.size() > 1 else token
|
||||
return [_build_token_tree_error(error, error_token.index), tokens]
|
||||
|
||||
match token.type:
|
||||
DMConstants.TOKEN_FUNCTION:
|
||||
var sub_tree = _build_token_tree(tokens, line_type, DMConstants.TOKEN_PARENS_CLOSE)
|
||||
|
||||
if sub_tree[0].size() > 0 and sub_tree[0][0].type == DMConstants.TOKEN_ERROR:
|
||||
return [_build_token_tree_error(sub_tree[0][0].value, sub_tree[0][0].index), tokens]
|
||||
|
||||
tree.append({
|
||||
type = DMConstants.TOKEN_FUNCTION,
|
||||
# Consume the trailing "("
|
||||
function = token.value.substr(0, token.value.length() - 1),
|
||||
value = _tokens_to_list(sub_tree[0]),
|
||||
i = token.index
|
||||
})
|
||||
tokens = sub_tree[1]
|
||||
|
||||
DMConstants.TOKEN_DICTIONARY_REFERENCE:
|
||||
var sub_tree = _build_token_tree(tokens, line_type, DMConstants.TOKEN_BRACKET_CLOSE)
|
||||
|
||||
if sub_tree[0].size() > 0 and sub_tree[0][0].type == DMConstants.TOKEN_ERROR:
|
||||
return [_build_token_tree_error(sub_tree[0][0].value, sub_tree[0][0].index), tokens]
|
||||
|
||||
var args = _tokens_to_list(sub_tree[0])
|
||||
if args.size() != 1:
|
||||
return [_build_token_tree_error(DMConstants.ERR_INVALID_INDEX, token.index), tokens]
|
||||
|
||||
tree.append({
|
||||
type = DMConstants.TOKEN_DICTIONARY_REFERENCE,
|
||||
# Consume the trailing "["
|
||||
variable = token.value.substr(0, token.value.length() - 1),
|
||||
value = args[0],
|
||||
i = token.index
|
||||
})
|
||||
tokens = sub_tree[1]
|
||||
|
||||
DMConstants.TOKEN_BRACE_OPEN:
|
||||
var sub_tree = _build_token_tree(tokens, line_type, DMConstants.TOKEN_BRACE_CLOSE)
|
||||
|
||||
if sub_tree[0].size() > 0 and sub_tree[0][0].type == DMConstants.TOKEN_ERROR:
|
||||
return [_build_token_tree_error(sub_tree[0][0].value, sub_tree[0][0].index), tokens]
|
||||
|
||||
var t = sub_tree[0]
|
||||
for i in range(0, t.size() - 2):
|
||||
# Convert Lua style dictionaries to string keys
|
||||
if t[i].type == DMConstants.TOKEN_VARIABLE and t[i+1].type == DMConstants.TOKEN_ASSIGNMENT:
|
||||
t[i].type = DMConstants.TOKEN_STRING
|
||||
t[i+1].type = DMConstants.TOKEN_COLON
|
||||
t[i+1].erase("value")
|
||||
|
||||
tree.append({
|
||||
type = DMConstants.TOKEN_DICTIONARY,
|
||||
value = _tokens_to_dictionary(sub_tree[0]),
|
||||
i = token.index
|
||||
})
|
||||
|
||||
tokens = sub_tree[1]
|
||||
|
||||
DMConstants.TOKEN_BRACKET_OPEN:
|
||||
var sub_tree = _build_token_tree(tokens, line_type, DMConstants.TOKEN_BRACKET_CLOSE)
|
||||
|
||||
if sub_tree[0].size() > 0 and sub_tree[0][0].type == DMConstants.TOKEN_ERROR:
|
||||
return [_build_token_tree_error(sub_tree[0][0].value, sub_tree[0][0].index), tokens]
|
||||
|
||||
var type = DMConstants.TOKEN_ARRAY
|
||||
var value = _tokens_to_list(sub_tree[0])
|
||||
|
||||
# See if this is referencing a nested dictionary value
|
||||
if tree.size() > 0:
|
||||
var previous_token = tree[tree.size() - 1]
|
||||
if previous_token.type in [DMConstants.TOKEN_DICTIONARY_REFERENCE, DMConstants.TOKEN_DICTIONARY_NESTED_REFERENCE]:
|
||||
type = DMConstants.TOKEN_DICTIONARY_NESTED_REFERENCE
|
||||
value = value[0]
|
||||
|
||||
tree.append({
|
||||
type = type,
|
||||
value = value,
|
||||
i = token.index
|
||||
})
|
||||
tokens = sub_tree[1]
|
||||
|
||||
DMConstants.TOKEN_PARENS_OPEN:
|
||||
var sub_tree = _build_token_tree(tokens, line_type, DMConstants.TOKEN_PARENS_CLOSE)
|
||||
|
||||
if sub_tree[0].size() > 0 and sub_tree[0][0].type == DMConstants.TOKEN_ERROR:
|
||||
return [_build_token_tree_error(sub_tree[0][0].value, sub_tree[0][0].index), tokens]
|
||||
|
||||
tree.append({
|
||||
type = DMConstants.TOKEN_GROUP,
|
||||
value = sub_tree[0],
|
||||
i = token.index
|
||||
})
|
||||
tokens = sub_tree[1]
|
||||
|
||||
DMConstants.TOKEN_PARENS_CLOSE, \
|
||||
DMConstants.TOKEN_BRACE_CLOSE, \
|
||||
DMConstants.TOKEN_BRACKET_CLOSE:
|
||||
if token.type != expected_close_token:
|
||||
return [_build_token_tree_error(DMConstants.ERR_UNEXPECTED_CLOSING_BRACKET, token.index), tokens]
|
||||
|
||||
tree.append({
|
||||
type = token.type,
|
||||
i = token.index
|
||||
})
|
||||
|
||||
return [tree, tokens]
|
||||
|
||||
DMConstants.TOKEN_NOT:
|
||||
# Double nots negate each other
|
||||
if tokens.size() > 0 and tokens.front().type == DMConstants.TOKEN_NOT:
|
||||
tokens.pop_front()
|
||||
else:
|
||||
tree.append({
|
||||
type = token.type,
|
||||
i = token.index
|
||||
})
|
||||
|
||||
DMConstants.TOKEN_COMMA, \
|
||||
DMConstants.TOKEN_COLON, \
|
||||
DMConstants.TOKEN_DOT:
|
||||
tree.append({
|
||||
type = token.type,
|
||||
i = token.index
|
||||
})
|
||||
|
||||
DMConstants.TOKEN_COMPARISON, \
|
||||
DMConstants.TOKEN_ASSIGNMENT, \
|
||||
DMConstants.TOKEN_OPERATOR, \
|
||||
DMConstants.TOKEN_AND_OR, \
|
||||
DMConstants.TOKEN_VARIABLE:
|
||||
var value = token.value.strip_edges()
|
||||
if value == "&&":
|
||||
value = "and"
|
||||
elif value == "||":
|
||||
value = "or"
|
||||
tree.append({
|
||||
type = token.type,
|
||||
value = value,
|
||||
i = token.index
|
||||
})
|
||||
|
||||
DMConstants.TOKEN_STRING:
|
||||
if token.value.begins_with("&"):
|
||||
tree.append({
|
||||
type = token.type,
|
||||
value = StringName(token.value.substr(2, token.value.length() - 3)),
|
||||
i = token.index
|
||||
})
|
||||
else:
|
||||
tree.append({
|
||||
type = token.type,
|
||||
value = token.value.substr(1, token.value.length() - 2),
|
||||
i = token.index
|
||||
})
|
||||
|
||||
DMConstants.TOKEN_CONDITION:
|
||||
return [_build_token_tree_error(DMConstants.ERR_UNEXPECTED_CONDITION, token.index), token]
|
||||
|
||||
DMConstants.TOKEN_BOOL:
|
||||
tree.append({
|
||||
type = token.type,
|
||||
value = token.value.to_lower() == "true",
|
||||
i = token.index
|
||||
})
|
||||
|
||||
DMConstants.TOKEN_NUMBER:
|
||||
var value = token.value.to_float() if "." in token.value else token.value.to_int()
|
||||
# If previous token is a number and this one is a negative number then
|
||||
# inject a minus operator token in between them.
|
||||
if tree.size() > 0 and token.value.begins_with("-") and tree[tree.size() - 1].type == DMConstants.TOKEN_NUMBER:
|
||||
tree.append(({
|
||||
type = DMConstants.TOKEN_OPERATOR,
|
||||
value = "-",
|
||||
i = token.index
|
||||
}))
|
||||
tree.append({
|
||||
type = token.type,
|
||||
value = -1 * value,
|
||||
i = token.index
|
||||
})
|
||||
else:
|
||||
tree.append({
|
||||
type = token.type,
|
||||
value = value,
|
||||
i = token.index
|
||||
})
|
||||
|
||||
if expected_close_token != "":
|
||||
var index: int = tokens[0].index if tokens.size() > 0 else 0
|
||||
return [_build_token_tree_error(DMConstants.ERR_MISSING_CLOSING_BRACKET, index), tokens]
|
||||
|
||||
return [tree, tokens]
|
||||
|
||||
|
||||
# Check the next token to see if it is valid to follow this one.
|
||||
func _check_next_token(token: Dictionary, next_tokens: Array[Dictionary], line_type: String, expected_close_token: String) -> Error:
|
||||
var next_token: Dictionary = { type = null }
|
||||
if next_tokens.size() > 0:
|
||||
next_token = next_tokens.front()
|
||||
|
||||
# Guard for assigning in a condition. If the assignment token isn't inside a Lua dictionary
|
||||
# then it's an unexpected assignment in a condition line.
|
||||
if token.type == DMConstants.TOKEN_ASSIGNMENT and line_type == DMConstants.TYPE_CONDITION and not next_tokens.any(func(t): return t.type == expected_close_token):
|
||||
return DMConstants.ERR_UNEXPECTED_ASSIGNMENT
|
||||
|
||||
# Special case for a negative number after this one
|
||||
if token.type == DMConstants.TOKEN_NUMBER and next_token.type == DMConstants.TOKEN_NUMBER and next_token.value.begins_with("-"):
|
||||
return OK
|
||||
|
||||
var expected_token_types = []
|
||||
var unexpected_token_types = []
|
||||
match token.type:
|
||||
DMConstants.TOKEN_FUNCTION, \
|
||||
DMConstants.TOKEN_PARENS_OPEN:
|
||||
unexpected_token_types = [
|
||||
null,
|
||||
DMConstants.TOKEN_COMMA,
|
||||
DMConstants.TOKEN_COLON,
|
||||
DMConstants.TOKEN_COMPARISON,
|
||||
DMConstants.TOKEN_ASSIGNMENT,
|
||||
DMConstants.TOKEN_OPERATOR,
|
||||
DMConstants.TOKEN_AND_OR,
|
||||
DMConstants.TOKEN_DOT
|
||||
]
|
||||
|
||||
DMConstants.TOKEN_BRACKET_CLOSE:
|
||||
unexpected_token_types = [
|
||||
DMConstants.TOKEN_NOT,
|
||||
DMConstants.TOKEN_BOOL,
|
||||
DMConstants.TOKEN_STRING,
|
||||
DMConstants.TOKEN_NUMBER,
|
||||
DMConstants.TOKEN_VARIABLE
|
||||
]
|
||||
|
||||
DMConstants.TOKEN_BRACE_OPEN:
|
||||
expected_token_types = [
|
||||
DMConstants.TOKEN_STRING,
|
||||
DMConstants.TOKEN_VARIABLE,
|
||||
DMConstants.TOKEN_NUMBER,
|
||||
DMConstants.TOKEN_BRACE_CLOSE
|
||||
]
|
||||
|
||||
DMConstants.TOKEN_PARENS_CLOSE, \
|
||||
DMConstants.TOKEN_BRACE_CLOSE:
|
||||
unexpected_token_types = [
|
||||
DMConstants.TOKEN_NOT,
|
||||
DMConstants.TOKEN_ASSIGNMENT,
|
||||
DMConstants.TOKEN_BOOL,
|
||||
DMConstants.TOKEN_STRING,
|
||||
DMConstants.TOKEN_NUMBER,
|
||||
DMConstants.TOKEN_VARIABLE
|
||||
]
|
||||
|
||||
DMConstants.TOKEN_COMPARISON, \
|
||||
DMConstants.TOKEN_OPERATOR, \
|
||||
DMConstants.TOKEN_COMMA, \
|
||||
DMConstants.TOKEN_DOT, \
|
||||
DMConstants.TOKEN_NOT, \
|
||||
DMConstants.TOKEN_AND_OR, \
|
||||
DMConstants.TOKEN_DICTIONARY_REFERENCE:
|
||||
unexpected_token_types = [
|
||||
null,
|
||||
DMConstants.TOKEN_COMMA,
|
||||
DMConstants.TOKEN_COLON,
|
||||
DMConstants.TOKEN_COMPARISON,
|
||||
DMConstants.TOKEN_ASSIGNMENT,
|
||||
DMConstants.TOKEN_OPERATOR,
|
||||
DMConstants.TOKEN_AND_OR,
|
||||
DMConstants.TOKEN_PARENS_CLOSE,
|
||||
DMConstants.TOKEN_BRACE_CLOSE,
|
||||
DMConstants.TOKEN_BRACKET_CLOSE,
|
||||
DMConstants.TOKEN_DOT
|
||||
]
|
||||
|
||||
DMConstants.TOKEN_COLON:
|
||||
unexpected_token_types = [
|
||||
DMConstants.TOKEN_COMMA,
|
||||
DMConstants.TOKEN_COLON,
|
||||
DMConstants.TOKEN_COMPARISON,
|
||||
DMConstants.TOKEN_ASSIGNMENT,
|
||||
DMConstants.TOKEN_OPERATOR,
|
||||
DMConstants.TOKEN_AND_OR,
|
||||
DMConstants.TOKEN_PARENS_CLOSE,
|
||||
DMConstants.TOKEN_BRACE_CLOSE,
|
||||
DMConstants.TOKEN_BRACKET_CLOSE,
|
||||
DMConstants.TOKEN_DOT
|
||||
]
|
||||
|
||||
DMConstants.TOKEN_BOOL, \
|
||||
DMConstants.TOKEN_STRING, \
|
||||
DMConstants.TOKEN_NUMBER:
|
||||
unexpected_token_types = [
|
||||
DMConstants.TOKEN_NOT,
|
||||
DMConstants.TOKEN_ASSIGNMENT,
|
||||
DMConstants.TOKEN_BOOL,
|
||||
DMConstants.TOKEN_STRING,
|
||||
DMConstants.TOKEN_NUMBER,
|
||||
DMConstants.TOKEN_VARIABLE,
|
||||
DMConstants.TOKEN_FUNCTION,
|
||||
DMConstants.TOKEN_PARENS_OPEN,
|
||||
DMConstants.TOKEN_BRACE_OPEN,
|
||||
DMConstants.TOKEN_BRACKET_OPEN
|
||||
]
|
||||
|
||||
DMConstants.TOKEN_VARIABLE:
|
||||
unexpected_token_types = [
|
||||
DMConstants.TOKEN_NOT,
|
||||
DMConstants.TOKEN_BOOL,
|
||||
DMConstants.TOKEN_STRING,
|
||||
DMConstants.TOKEN_NUMBER,
|
||||
DMConstants.TOKEN_VARIABLE,
|
||||
DMConstants.TOKEN_FUNCTION,
|
||||
DMConstants.TOKEN_PARENS_OPEN,
|
||||
DMConstants.TOKEN_BRACE_OPEN,
|
||||
DMConstants.TOKEN_BRACKET_OPEN
|
||||
]
|
||||
|
||||
if (expected_token_types.size() > 0 and not next_token.type in expected_token_types or unexpected_token_types.size() > 0 and next_token.type in unexpected_token_types):
|
||||
match next_token.type:
|
||||
null:
|
||||
return DMConstants.ERR_UNEXPECTED_END_OF_EXPRESSION
|
||||
|
||||
DMConstants.TOKEN_FUNCTION:
|
||||
return DMConstants.ERR_UNEXPECTED_FUNCTION
|
||||
|
||||
DMConstants.TOKEN_PARENS_OPEN, \
|
||||
DMConstants.TOKEN_PARENS_CLOSE:
|
||||
return DMConstants.ERR_UNEXPECTED_BRACKET
|
||||
|
||||
DMConstants.TOKEN_COMPARISON, \
|
||||
DMConstants.TOKEN_ASSIGNMENT, \
|
||||
DMConstants.TOKEN_OPERATOR, \
|
||||
DMConstants.TOKEN_NOT, \
|
||||
DMConstants.TOKEN_AND_OR:
|
||||
return DMConstants.ERR_UNEXPECTED_OPERATOR
|
||||
|
||||
DMConstants.TOKEN_COMMA:
|
||||
return DMConstants.ERR_UNEXPECTED_COMMA
|
||||
DMConstants.TOKEN_COLON:
|
||||
return DMConstants.ERR_UNEXPECTED_COLON
|
||||
DMConstants.TOKEN_DOT:
|
||||
return DMConstants.ERR_UNEXPECTED_DOT
|
||||
|
||||
DMConstants.TOKEN_BOOL:
|
||||
return DMConstants.ERR_UNEXPECTED_BOOLEAN
|
||||
DMConstants.TOKEN_STRING:
|
||||
return DMConstants.ERR_UNEXPECTED_STRING
|
||||
DMConstants.TOKEN_NUMBER:
|
||||
return DMConstants.ERR_UNEXPECTED_NUMBER
|
||||
DMConstants.TOKEN_VARIABLE:
|
||||
return DMConstants.ERR_UNEXPECTED_VARIABLE
|
||||
|
||||
return DMConstants.ERR_INVALID_EXPRESSION
|
||||
|
||||
return OK
|
||||
|
||||
|
||||
# Convert a series of comma separated tokens to an [Array].
|
||||
func _tokens_to_list(tokens: Array[Dictionary]) -> Array[Array]:
|
||||
var list: Array[Array] = []
|
||||
var current_item: Array[Dictionary] = []
|
||||
for token in tokens:
|
||||
if token.type == DMConstants.TOKEN_COMMA:
|
||||
list.append(current_item)
|
||||
current_item = []
|
||||
else:
|
||||
current_item.append(token)
|
||||
|
||||
if current_item.size() > 0:
|
||||
list.append(current_item)
|
||||
|
||||
return list
|
||||
|
||||
|
||||
# Convert a series of key/value tokens into a [Dictionary]
|
||||
func _tokens_to_dictionary(tokens: Array[Dictionary]) -> Dictionary:
|
||||
var dictionary = {}
|
||||
for i in range(0, tokens.size()):
|
||||
if tokens[i].type == DMConstants.TOKEN_COLON:
|
||||
if tokens.size() == i + 2:
|
||||
dictionary[tokens[i - 1]] = tokens[i + 1]
|
||||
else:
|
||||
dictionary[tokens[i - 1]] = { type = DMConstants.TOKEN_GROUP, value = tokens.slice(i + 1), i = tokens[0].i }
|
||||
|
||||
return dictionary
|
||||
|
||||
|
||||
# Work out what the next token is from a string.
|
||||
func _find_match(input: String) -> Dictionary:
|
||||
for key in regex.TOKEN_DEFINITIONS.keys():
|
||||
var regex = regex.TOKEN_DEFINITIONS.get(key)
|
||||
var found = regex.search(input)
|
||||
if found:
|
||||
return {
|
||||
type = key,
|
||||
remaining_text = input.substr(found.strings[0].length()),
|
||||
value = found.strings[0]
|
||||
}
|
||||
|
||||
return {}
|
||||
|
||||
|
||||
#endregion
|
||||
@@ -0,0 +1 @@
|
||||
uid://dbi4hbar8ubwu
|
||||
@@ -0,0 +1,68 @@
|
||||
## Data associated with a dialogue jump/goto line.
|
||||
class_name DMResolvedGotoData extends RefCounted
|
||||
|
||||
|
||||
## The title that was specified
|
||||
var title: String = ""
|
||||
## The target line's ID
|
||||
var next_id: String = ""
|
||||
## An expression to determine the target line at runtime.
|
||||
var expression: Array[Dictionary] = []
|
||||
## The given line text with the jump syntax removed.
|
||||
var text_without_goto: String = ""
|
||||
## Whether this is a jump-and-return style jump.
|
||||
var is_snippet: bool = false
|
||||
## A parse error if there was one.
|
||||
var error: int
|
||||
## The index in the string where
|
||||
var index: int = 0
|
||||
|
||||
# An instance of the compiler [RegEx] list.
|
||||
var regex: DMCompilerRegEx = DMCompilerRegEx.new()
|
||||
|
||||
|
||||
func _init(text: String, titles: Dictionary) -> void:
|
||||
if not "=> " in text and not "=>< " in text: return
|
||||
|
||||
if "=> " in text:
|
||||
text_without_goto = text.substr(0, text.find("=> ")).strip_edges()
|
||||
elif "=>< " in text:
|
||||
is_snippet = true
|
||||
text_without_goto = text.substr(0, text.find("=>< ")).strip_edges()
|
||||
|
||||
var found: RegExMatch = regex.GOTO_REGEX.search(text)
|
||||
if found == null:
|
||||
return
|
||||
|
||||
title = found.strings[found.names.goto].strip_edges()
|
||||
index = found.get_start(0)
|
||||
|
||||
if title == "":
|
||||
error = DMConstants.ERR_UNKNOWN_TITLE
|
||||
return
|
||||
|
||||
# "=> END!" means end the conversation, ignoring any "=><" chains.
|
||||
if title == "END!":
|
||||
next_id = DMConstants.ID_END_CONVERSATION
|
||||
|
||||
# "=> END" means end the current title (and go back to the previous one if there is one
|
||||
# in the stack)
|
||||
elif title == "END":
|
||||
next_id = DMConstants.ID_END
|
||||
|
||||
elif titles.has(title):
|
||||
next_id = titles.get(title)
|
||||
elif title.begins_with("{{"):
|
||||
var expression_parser: DMExpressionParser = DMExpressionParser.new()
|
||||
var title_expression: Array[Dictionary] = expression_parser.extract_replacements(title, 0)
|
||||
if title_expression[0].has("error"):
|
||||
error = title_expression[0].error
|
||||
else:
|
||||
expression = title_expression[0].expression
|
||||
else:
|
||||
next_id = title
|
||||
error = DMConstants.ERR_UNKNOWN_TITLE
|
||||
|
||||
|
||||
func _to_string() -> String:
|
||||
return "%s =>%s %s (%s)" % [text_without_goto, "<" if is_snippet else "", title, next_id]
|
||||
@@ -0,0 +1 @@
|
||||
uid://llhl5pt47eoq
|
||||
@@ -0,0 +1,167 @@
|
||||
## Any data associated with inline dialogue BBCodes.
|
||||
class_name DMResolvedLineData extends RefCounted
|
||||
|
||||
## The line's text
|
||||
var text: String = ""
|
||||
## A map of pauses against where they are found in the text.
|
||||
var pauses: Dictionary = {}
|
||||
## A map of speed changes against where they are found in the text.
|
||||
var speeds: Dictionary = {}
|
||||
## A list of any mutations to run and where they are found in the text.
|
||||
var mutations: Array[Array] = []
|
||||
## A duration reference for the line. Represented as "auto" or a stringified number.
|
||||
var time: String = ""
|
||||
|
||||
|
||||
func _init(line: String) -> void:
|
||||
text = line
|
||||
pauses = {}
|
||||
speeds = {}
|
||||
mutations = []
|
||||
time = ""
|
||||
|
||||
var bbcodes: Array = []
|
||||
|
||||
# Remove any escaped brackets (ie. "\[")
|
||||
var escaped_open_brackets: PackedInt32Array = []
|
||||
var escaped_close_brackets: PackedInt32Array = []
|
||||
for i in range(0, text.length() - 1):
|
||||
if text.substr(i, 2) == "\\[":
|
||||
text = text.substr(0, i) + "!" + text.substr(i + 2)
|
||||
escaped_open_brackets.append(i)
|
||||
elif text.substr(i, 2) == "\\]":
|
||||
text = text.substr(0, i) + "!" + text.substr(i + 2)
|
||||
escaped_close_brackets.append(i)
|
||||
|
||||
# Extract all of the BB codes so that we know the actual text (we could do this easier with
|
||||
# a RichTextLabel but then we'd need to await idle_frame which is annoying)
|
||||
var bbcode_positions = find_bbcode_positions_in_string(text)
|
||||
var accumulaive_length_offset = 0
|
||||
for position in bbcode_positions:
|
||||
# Ignore our own markers
|
||||
if position.code in ["wait", "speed", "/speed", "do", "do!", "set", "next", "if", "else", "/if"]:
|
||||
continue
|
||||
|
||||
bbcodes.append({
|
||||
bbcode = position.bbcode,
|
||||
start = position.start,
|
||||
offset_start = position.start - accumulaive_length_offset
|
||||
})
|
||||
accumulaive_length_offset += position.bbcode.length()
|
||||
|
||||
for bb in bbcodes:
|
||||
text = text.substr(0, bb.offset_start) + text.substr(bb.offset_start + bb.bbcode.length())
|
||||
|
||||
# Now find any dialogue markers
|
||||
var next_bbcode_position = find_bbcode_positions_in_string(text, false)
|
||||
var limit = 0
|
||||
while next_bbcode_position.size() > 0 and limit < 1000:
|
||||
limit += 1
|
||||
|
||||
var bbcode = next_bbcode_position[0]
|
||||
|
||||
var index = bbcode.start
|
||||
var code = bbcode.code
|
||||
var raw_args = bbcode.raw_args
|
||||
var args = {}
|
||||
if code in ["do", "do!", "set"]:
|
||||
var compilation: DMCompilation = DMCompilation.new()
|
||||
args["value"] = compilation.extract_mutation("%s %s" % [code, raw_args])
|
||||
else:
|
||||
# Could be something like:
|
||||
# "=1.0"
|
||||
# " rate=20 level=10"
|
||||
if raw_args and raw_args[0] == "=":
|
||||
raw_args = "value" + raw_args
|
||||
for pair in raw_args.strip_edges().split(" "):
|
||||
if "=" in pair:
|
||||
var bits = pair.split("=")
|
||||
args[bits[0]] = bits[1]
|
||||
|
||||
match code:
|
||||
"wait":
|
||||
if pauses.has(index):
|
||||
pauses[index] += args.get("value").to_float()
|
||||
else:
|
||||
pauses[index] = args.get("value").to_float()
|
||||
"speed":
|
||||
speeds[index] = args.get("value").to_float()
|
||||
"/speed":
|
||||
speeds[index] = 1.0
|
||||
"do", "do!", "set":
|
||||
mutations.append([index, args.get("value")])
|
||||
"next":
|
||||
time = args.get("value") if args.has("value") else "0"
|
||||
|
||||
# Find any BB codes that are after this index and remove the length from their start
|
||||
var length = bbcode.bbcode.length()
|
||||
for bb in bbcodes:
|
||||
if bb.offset_start > bbcode.start:
|
||||
bb.offset_start -= length
|
||||
bb.start -= length
|
||||
|
||||
# Find any escaped brackets after this that need moving
|
||||
for i in range(0, escaped_open_brackets.size()):
|
||||
if escaped_open_brackets[i] > bbcode.start:
|
||||
escaped_open_brackets[i] -= length
|
||||
for i in range(0, escaped_close_brackets.size()):
|
||||
if escaped_close_brackets[i] > bbcode.start:
|
||||
escaped_close_brackets[i] -= length
|
||||
|
||||
text = text.substr(0, index) + text.substr(index + length)
|
||||
next_bbcode_position = find_bbcode_positions_in_string(text, false)
|
||||
|
||||
# Put the BB Codes back in
|
||||
for bb in bbcodes:
|
||||
text = text.insert(bb.start, bb.bbcode)
|
||||
|
||||
# Put the escaped brackets back in
|
||||
for index in escaped_open_brackets:
|
||||
text = text.left(index) + "[" + text.right(text.length() - index - 1)
|
||||
for index in escaped_close_brackets:
|
||||
text = text.left(index) + "]" + text.right(text.length() - index - 1)
|
||||
|
||||
|
||||
func find_bbcode_positions_in_string(string: String, find_all: bool = true, include_conditions: bool = false) -> Array[Dictionary]:
|
||||
if not "[" in string: return []
|
||||
|
||||
var positions: Array[Dictionary] = []
|
||||
|
||||
var open_brace_count: int = 0
|
||||
var start: int = 0
|
||||
var bbcode: String = ""
|
||||
var code: String = ""
|
||||
var is_finished_code: bool = false
|
||||
for i in range(0, string.length()):
|
||||
if string[i] == "[":
|
||||
if open_brace_count == 0:
|
||||
start = i
|
||||
bbcode = ""
|
||||
code = ""
|
||||
is_finished_code = false
|
||||
open_brace_count += 1
|
||||
|
||||
else:
|
||||
if not is_finished_code and (string[i].to_upper() != string[i] or string[i] == "/" or string[i] == "!"):
|
||||
code += string[i]
|
||||
else:
|
||||
is_finished_code = true
|
||||
|
||||
if open_brace_count > 0:
|
||||
bbcode += string[i]
|
||||
|
||||
if string[i] == "]":
|
||||
open_brace_count -= 1
|
||||
if open_brace_count == 0 and (include_conditions or not code in ["if", "else", "/if"]):
|
||||
positions.append({
|
||||
bbcode = bbcode,
|
||||
code = code,
|
||||
start = start,
|
||||
end = i,
|
||||
raw_args = bbcode.substr(code.length() + 1, bbcode.length() - code.length() - 2).strip_edges()
|
||||
})
|
||||
|
||||
if not find_all:
|
||||
return positions
|
||||
|
||||
return positions
|
||||
@@ -0,0 +1 @@
|
||||
uid://0k6q8kukq0qa
|
||||
@@ -0,0 +1,26 @@
|
||||
## Tag data associated with a line of dialogue.
|
||||
class_name DMResolvedTagData extends RefCounted
|
||||
|
||||
|
||||
## The list of tags.
|
||||
var tags: PackedStringArray = []
|
||||
## The line with any tag syntax removed.
|
||||
var text_without_tags: String = ""
|
||||
|
||||
# An instance of the compiler [RegEx].
|
||||
var regex: DMCompilerRegEx = DMCompilerRegEx.new()
|
||||
|
||||
|
||||
func _init(text: String) -> void:
|
||||
var resolved_tags: PackedStringArray = []
|
||||
var tag_matches: Array[RegExMatch] = regex.TAGS_REGEX.search_all(text)
|
||||
for tag_match in tag_matches:
|
||||
text = text.replace(tag_match.get_string(), "")
|
||||
var tags = tag_match.get_string().replace("[#", "").replace("]", "").replace(", ", ",").split(",")
|
||||
for tag in tags:
|
||||
tag = tag.replace("#", "")
|
||||
if not tag in resolved_tags:
|
||||
resolved_tags.append(tag)
|
||||
|
||||
tags = resolved_tags
|
||||
text_without_tags = text
|
||||
@@ -0,0 +1 @@
|
||||
uid://cqai3ikuilqfq
|
||||
@@ -0,0 +1,44 @@
|
||||
## An intermediate representation of a dialogue line before it gets compiled.
|
||||
class_name DMTreeLine extends RefCounted
|
||||
|
||||
|
||||
## The line number where this dialogue was found (after imported files have had their content imported).
|
||||
var line_number: int = 0
|
||||
## The parent [DMTreeLine] of this line.
|
||||
## This is stored as a Weak Reference so that this RefCounted can elegantly free itself.
|
||||
## Without it being a Weak Reference, this can easily cause a cyclical reference that keeps this resource alive.
|
||||
var parent: WeakRef
|
||||
## The ID of this line.
|
||||
var id: String
|
||||
## The type of this line (as a [String] defined in [DMConstants].
|
||||
var type: String = ""
|
||||
## Is this line part of a randomised group?
|
||||
var is_random: bool = false
|
||||
## The indent count for this line.
|
||||
var indent: int = 0
|
||||
## The text of this line.
|
||||
var text: String = ""
|
||||
## The child [DMTreeLine]s of this line.
|
||||
var children: Array[DMTreeLine] = []
|
||||
## Any doc comments attached to this line.
|
||||
var notes: String = ""
|
||||
|
||||
|
||||
func _init(initial_id: String) -> void:
|
||||
id = initial_id
|
||||
|
||||
|
||||
func _to_string() -> String:
|
||||
var tabs = []
|
||||
tabs.resize(indent)
|
||||
tabs.fill("\t")
|
||||
tabs = "".join(tabs)
|
||||
|
||||
return tabs.join([tabs + "{\n",
|
||||
"\tid: %s\n" % [id],
|
||||
"\ttype: %s\n" % [type],
|
||||
"\tis_random: %s\n" % ["true" if is_random else "false"],
|
||||
"\ttext: %s\n" % [text],
|
||||
"\tnotes: %s\n" % [notes],
|
||||
"\tchildren: []\n" if children.size() == 0 else "\tchildren: [\n" + ",\n".join(children.map(func(child): return str(child))) + "]\n",
|
||||
"}"])
|
||||
@@ -0,0 +1 @@
|
||||
uid://dsu4i84dpif14
|
||||
@@ -1,5 +1,5 @@
|
||||
@tool
|
||||
extends CodeEdit
|
||||
class_name DMCodeEdit extends CodeEdit
|
||||
|
||||
|
||||
signal active_title_change(title: String)
|
||||
@@ -7,10 +7,6 @@ signal error_clicked(line_number: int)
|
||||
signal external_file_requested(path: String, title: String)
|
||||
|
||||
|
||||
const DialogueManagerParser = preload("./parser.gd")
|
||||
const DialogueSyntaxHighlighter = preload("./code_edit_syntax_highlighter.gd")
|
||||
|
||||
|
||||
# A link back to the owner `MainView`
|
||||
var main_view
|
||||
|
||||
@@ -19,7 +15,7 @@ var theme_overrides: Dictionary:
|
||||
set(value):
|
||||
theme_overrides = value
|
||||
|
||||
syntax_highlighter = DialogueSyntaxHighlighter.new()
|
||||
syntax_highlighter = DMSyntaxHighlighter.new()
|
||||
|
||||
# General UI
|
||||
add_theme_color_override("font_color", theme_overrides.text_color)
|
||||
@@ -67,7 +63,7 @@ func _ready() -> void:
|
||||
if not has_comment_delimiter("#"):
|
||||
add_comment_delimiter("#", "", true)
|
||||
|
||||
syntax_highlighter = DialogueSyntaxHighlighter.new()
|
||||
syntax_highlighter = DMSyntaxHighlighter.new()
|
||||
|
||||
|
||||
func _gui_input(event: InputEvent) -> void:
|
||||
@@ -111,18 +107,19 @@ func _can_drop_data(at_position: Vector2, data) -> bool:
|
||||
if typeof(data) != TYPE_DICTIONARY: return false
|
||||
if data.type != "files": return false
|
||||
|
||||
var files: PackedStringArray = Array(data.files).filter(func(f): return f.get_extension() == "dialogue")
|
||||
var files: PackedStringArray = Array(data.files)
|
||||
return files.size() > 0
|
||||
|
||||
|
||||
func _drop_data(at_position: Vector2, data) -> void:
|
||||
var replace_regex: RegEx = RegEx.create_from_string("[^a-zA-Z_0-9]+")
|
||||
|
||||
var files: PackedStringArray = Array(data.files).filter(func(f): return f.get_extension() == "dialogue")
|
||||
var files: PackedStringArray = Array(data.files)
|
||||
for file in files:
|
||||
# Don't import the file into itself
|
||||
if file == main_view.current_file_path: continue
|
||||
|
||||
if file.get_extension() == "dialogue":
|
||||
var path = file.replace("res://", "").replace(".dialogue", "")
|
||||
# Find the first non-import line in the file to add our import
|
||||
var lines = text.split("\n")
|
||||
@@ -131,6 +128,13 @@ func _drop_data(at_position: Vector2, data) -> void:
|
||||
insert_line_at(i, "import \"%s\" as %s\n" % [file, replace_regex.sub(path, "_", true)])
|
||||
set_caret_line(i)
|
||||
break
|
||||
else:
|
||||
var cursor: Vector2 = get_line_column_at_pos(at_position)
|
||||
if cursor.x > -1 and cursor.y > -1:
|
||||
set_cursor(cursor)
|
||||
remove_secondary_carets()
|
||||
insert_text("\"%s\"" % file, cursor.y, cursor.x)
|
||||
grab_focus()
|
||||
|
||||
|
||||
func _request_code_completion(force: bool) -> void:
|
||||
@@ -151,17 +155,18 @@ func _request_code_completion(force: bool) -> void:
|
||||
add_code_completion_option(CodeEdit.KIND_CLASS, "END!", "END!".substr(prompt.length()), theme_overrides.text_color, get_theme_icon("Stop", "EditorIcons"))
|
||||
|
||||
# Get all titles, including those in imports
|
||||
var parser: DialogueManagerParser = DialogueManagerParser.new()
|
||||
parser.prepare(text, main_view.current_file_path, false)
|
||||
for title in parser.titles:
|
||||
if "/" in title:
|
||||
for title: String in DMCompiler.get_titles_in_text(text, main_view.current_file_path):
|
||||
# Ignore any imported titles that aren't resolved to human readable.
|
||||
if title.to_int() > 0:
|
||||
continue
|
||||
|
||||
elif "/" in title:
|
||||
var bits = title.split("/")
|
||||
if matches_prompt(prompt, bits[0]) or matches_prompt(prompt, bits[1]):
|
||||
add_code_completion_option(CodeEdit.KIND_CLASS, title, title.substr(prompt.length()), theme_overrides.text_color, get_theme_icon("CombineLines", "EditorIcons"))
|
||||
elif matches_prompt(prompt, title):
|
||||
add_code_completion_option(CodeEdit.KIND_CLASS, title, title.substr(prompt.length()), theme_overrides.text_color, get_theme_icon("ArrowRight", "EditorIcons"))
|
||||
update_code_completion_options(true)
|
||||
parser.free()
|
||||
return
|
||||
|
||||
var name_so_far: String = WEIGHTED_RANDOM_PREFIX.sub(current_line.strip_edges(), "")
|
||||
@@ -205,8 +210,8 @@ func get_cursor() -> Vector2:
|
||||
|
||||
# Set the caret from a Vector2
|
||||
func set_cursor(from_cursor: Vector2) -> void:
|
||||
set_caret_line(from_cursor.y)
|
||||
set_caret_column(from_cursor.x)
|
||||
set_caret_line(from_cursor.y, false)
|
||||
set_caret_column(from_cursor.x, false)
|
||||
|
||||
|
||||
# Check if a prompt is the start of a string without actually being that string
|
||||
@@ -219,8 +224,9 @@ func get_titles() -> PackedStringArray:
|
||||
var titles = PackedStringArray([])
|
||||
var lines = text.split("\n")
|
||||
for line in lines:
|
||||
if line.begins_with("~ "):
|
||||
titles.append(line.substr(2).strip_edges())
|
||||
if line.strip_edges().begins_with("~ "):
|
||||
titles.append(line.strip_edges().substr(2))
|
||||
|
||||
return titles
|
||||
|
||||
|
||||
@@ -259,6 +265,11 @@ func get_character_names(beginning_with: String) -> PackedStringArray:
|
||||
|
||||
# Mark a line as an error or not
|
||||
func mark_line_as_error(line_number: int, is_error: bool) -> void:
|
||||
# Lines display counting from 1 but are actually indexed from 0
|
||||
line_number -= 1
|
||||
|
||||
if line_number < 0: return
|
||||
|
||||
if is_error:
|
||||
set_line_background_color(line_number, theme_overrides.error_line_color)
|
||||
set_line_gutter_icon(line_number, 0, get_theme_icon("StatusError", "EditorIcons"))
|
||||
@@ -372,6 +383,7 @@ func delete_current_line() -> void:
|
||||
func move_line(offset: int) -> void:
|
||||
offset = clamp(offset, -1, 1)
|
||||
|
||||
var starting_scroll := scroll_vertical
|
||||
var cursor = get_cursor()
|
||||
var reselect: bool = false
|
||||
var from: int = cursor.y
|
||||
@@ -395,12 +407,14 @@ func move_line(offset: int) -> void:
|
||||
text = "\n".join(lines)
|
||||
|
||||
cursor.y += offset
|
||||
set_cursor(cursor)
|
||||
from += offset
|
||||
to += offset
|
||||
if reselect:
|
||||
select(from, 0, to, get_line_width(to))
|
||||
set_cursor(cursor)
|
||||
|
||||
text_changed.emit()
|
||||
scroll_vertical = starting_scroll + offset
|
||||
|
||||
|
||||
### Signals
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://1ymv6jff0eay
|
||||
uid://djeybvlb332mp
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://civ6shmka5e8u"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/dialogue_manager/components/code_edit_syntax_highlighter.gd" id="1_58cfo"]
|
||||
[ext_resource type="Script" path="res://addons/dialogue_manager/components/code_edit.gd" id="1_g324i"]
|
||||
[ext_resource type="Script" uid="uid://klpiq4tk3t7a" path="res://addons/dialogue_manager/components/code_edit_syntax_highlighter.gd" id="1_58cfo"]
|
||||
[ext_resource type="Script" uid="uid://djeybvlb332mp" path="res://addons/dialogue_manager/components/code_edit.gd" id="1_g324i"]
|
||||
|
||||
[sub_resource type="SyntaxHighlighter" id="SyntaxHighlighter_cobxx"]
|
||||
script = ExtResource("1_58cfo")
|
||||
|
||||
@@ -1,55 +1,18 @@
|
||||
@tool
|
||||
extends SyntaxHighlighter
|
||||
class_name DMSyntaxHighlighter extends SyntaxHighlighter
|
||||
|
||||
|
||||
const DialogueManagerParser = preload("./parser.gd")
|
||||
|
||||
|
||||
enum ExpressionType {DO, SET, IF}
|
||||
|
||||
|
||||
var dialogue_manager_parser: DialogueManagerParser = DialogueManagerParser.new()
|
||||
|
||||
var regex_titles: RegEx = RegEx.create_from_string("^\\s*(?<title>~\\s+[^\\!\\@\\#\\$\\%\\^\\&\\*\\(\\)\\-\\=\\+\\{\\}\\[\\]\\;\\:\\\"\\'\\,\\.\\<\\>\\?\\/\\s]+)")
|
||||
var regex_comments: RegEx = RegEx.create_from_string("(?:(?>\"(?:\\\\\"|[^\"\\n])*\")[^\"\\n]*?\\s*(?<comment>#[^\\n]*)$|^[^\"#\\n]*?\\s*(?<comment2>#[^\\n]*))")
|
||||
var regex_mutation: RegEx = RegEx.create_from_string("^\\s*(do|do!|set) (?<mutation>.*)")
|
||||
var regex_condition: RegEx = RegEx.create_from_string("^\\s*(if|elif|while|else if) (?<condition>.*)")
|
||||
var regex_wcondition: RegEx = RegEx.create_from_string("\\[if (?<condition>((?:[^\\[\\]]*)|(?:\\[(?1)\\]))*?)\\]")
|
||||
var regex_wendif: RegEx = RegEx.create_from_string("\\[(\\/if|else)\\]")
|
||||
var regex_rgroup: RegEx = RegEx.create_from_string("\\[\\[(?<options>.*?)\\]\\]")
|
||||
var regex_endconditions: RegEx = RegEx.create_from_string("^\\s*(endif|else):?\\s*$")
|
||||
var regex_tags: RegEx = RegEx.create_from_string("\\[(?<tag>(?!(?:ID:.*)|if)[a-zA-Z_][a-zA-Z0-9_]*!?)(?:[= ](?<val>[^\\[\\]]+))?\\](?:(?<text>(?!\\[\\/\\k<tag>\\]).*?)?(?<end>\\[\\/\\k<tag>\\]))?")
|
||||
var regex_dialogue: RegEx = RegEx.create_from_string("^\\s*(?:(?<random>\\%[\\d.]* )|(?<response>- ))?(?:(?<character>[^#:]*): )?(?<dialogue>.*)$")
|
||||
var regex_goto: RegEx = RegEx.create_from_string("=><? (?:(?<file>[^\\/]+)\\/)?(?<title>[^\\/]*)")
|
||||
var regex_string: RegEx = RegEx.create_from_string("^&?(?<delimiter>[\"'])(?<content>(?:\\\\{2})*|(?:.*?[^\\\\](?:\\\\{2})*))\\1$")
|
||||
var regex_escape: RegEx = RegEx.create_from_string("\\\\.")
|
||||
var regex_number: RegEx = RegEx.create_from_string("^-?(?:(?:0x(?:[0-9A-Fa-f]{2})+)|(?:0b[01]+)|(?:\\d+(?:(?:[\\.]\\d*)?(?:e\\d+)?)|(?:_\\d+)+)?)$")
|
||||
var regex_array: RegEx = RegEx.create_from_string("\\[((?>[^\\[\\]]+|(?R))*)\\]")
|
||||
var regex_dict: RegEx = RegEx.create_from_string("^\\{((?>[^\\{\\}]+|(?R))*)\\}$")
|
||||
var regex_kvdict: RegEx = RegEx.create_from_string("^\\s*(?<left>.*?)\\s*(?<colon>:|=)\\s*(?<right>[^\\/]+)$")
|
||||
var regex_commas: RegEx = RegEx.create_from_string("([^,]+)(?:\\s*,\\s*)?")
|
||||
var regex_assignment: RegEx = RegEx.create_from_string("^\\s*(?<var>[a-zA-Z_][a-zA-Z_0-9]*)(?:(?<attr>(?:\\.[a-zA-Z_][a-zA-Z_0-9]*)+)|(?:\\[(?<key>[^\\]]+)\\]))?\\s*(?<op>(?:\\/|\\*|-|\\+)?=)\\s*(?<val>.*)$")
|
||||
var regex_varname: RegEx = RegEx.create_from_string("^\\s*(?!true|false|and|or|&&|\\|\\|not|in|null)(?<var>[a-zA-Z_][a-zA-Z_0-9]*)(?:(?<attr>(?:\\.[a-zA-Z_][a-zA-Z_0-9]*)+)|(?:\\[(?<key>[^\\]]+)\\]))?\\s*$")
|
||||
var regex_keyword: RegEx = RegEx.create_from_string("^\\s*(true|false|null)\\s*$")
|
||||
var regex_function: RegEx = RegEx.create_from_string("^\\s*([a-zA-Z_][a-zA-Z_0-9]*\\s*)\\(")
|
||||
var regex_comparison: RegEx = RegEx.create_from_string("^(?<left>.*?)\\s*(?<op>==|>=|<=|<|>|!=)\\s*(?<right>.*)$")
|
||||
var regex_blogical: RegEx = RegEx.create_from_string("^(?<left>.*?)\\s+(?<op>and|or|in|&&|\\|\\|)\\s+(?<right>.*)$")
|
||||
var regex_ulogical: RegEx = RegEx.create_from_string("^\\s*(?<op>not)\\s+(?<right>.*)$")
|
||||
var regex_paren: RegEx = RegEx.create_from_string("\\((?<paren>((?:[^\\(\\)]*)|(?:\\((?1)\\)))*?)\\)")
|
||||
var regex: DMCompilerRegEx = DMCompilerRegEx.new()
|
||||
var compilation: DMCompilation = DMCompilation.new()
|
||||
var expression_parser = DMExpressionParser.new()
|
||||
|
||||
var cache: Dictionary = {}
|
||||
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_PREDELETE:
|
||||
dialogue_manager_parser.free()
|
||||
|
||||
|
||||
func _clear_highlighting_cache() -> void:
|
||||
cache = {}
|
||||
cache.clear()
|
||||
|
||||
|
||||
## Returns the syntax coloring for a dialogue file line
|
||||
func _get_line_syntax_highlighting(line: int) -> Dictionary:
|
||||
var colors: Dictionary = {}
|
||||
var text_edit: TextEdit = get_text_edit()
|
||||
@@ -63,323 +26,194 @@ func _get_line_syntax_highlighting(line: int) -> Dictionary:
|
||||
if text in cache:
|
||||
return cache[text]
|
||||
|
||||
# Comments have to be removed to make the remaining processing easier.
|
||||
# Count both end-of-line and single-line comments
|
||||
# Comments are not allowed within dialogue lines or response lines, so we ask the parser what it thinks the current line is
|
||||
if not (dialogue_manager_parser.is_dialogue_line(text) or dialogue_manager_parser.is_response_line(text)) or dialogue_manager_parser.is_line_empty(text) or dialogue_manager_parser.is_import_line(text):
|
||||
var comment_matches: Array[RegExMatch] = regex_comments.search_all(text)
|
||||
for comment_match in comment_matches:
|
||||
for i in ["comment", "comment2"]:
|
||||
if i in comment_match.names:
|
||||
colors[comment_match.get_start(i)] = {"color": text_edit.theme_overrides.comments_color}
|
||||
text = text.substr(0, comment_match.get_start(i))
|
||||
var theme: Dictionary = text_edit.theme_overrides
|
||||
|
||||
# Dialogues
|
||||
var dialogue_matches: Array[RegExMatch] = regex_dialogue.search_all(text)
|
||||
for dialogue_match in dialogue_matches:
|
||||
if "random" in dialogue_match.names:
|
||||
colors[dialogue_match.get_start("random")] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
colors[dialogue_match.get_end("random")] = {"color": text_edit.theme_overrides.text_color}
|
||||
if "response" in dialogue_match.names:
|
||||
colors[dialogue_match.get_start("response")] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
colors[dialogue_match.get_end("response")] = {"color": text_edit.theme_overrides.text_color}
|
||||
if "character" in dialogue_match.names:
|
||||
colors[dialogue_match.get_start("character")] = {"color": text_edit.theme_overrides.members_color}
|
||||
colors[dialogue_match.get_end("character")] = {"color": text_edit.theme_overrides.text_color}
|
||||
colors.merge(_get_dialogue_syntax_highlighting(dialogue_match.get_start("dialogue"), dialogue_match.get_string("dialogue")), true)
|
||||
var index: int = 0
|
||||
|
||||
# Title lines
|
||||
if dialogue_manager_parser.is_title_line(text):
|
||||
var title_matches: Array[RegExMatch] = regex_titles.search_all(text)
|
||||
for title_match in title_matches:
|
||||
colors[title_match.get_start("title")] = {"color": text_edit.theme_overrides.titles_color}
|
||||
match DMCompiler.get_line_type(text):
|
||||
DMConstants.TYPE_USING:
|
||||
colors[index] = { color = theme.conditions_color }
|
||||
colors[index + "using ".length()] = { color = theme.text_color }
|
||||
|
||||
# Import lines
|
||||
var import_matches: Array[RegExMatch] = dialogue_manager_parser.IMPORT_REGEX.search_all(text)
|
||||
for import_match in import_matches:
|
||||
colors[import_match.get_start(0)] = {"color": text_edit.theme_overrides.conditions_color}
|
||||
colors[import_match.get_start("path") - 1] = {"color": text_edit.theme_overrides.strings_color}
|
||||
colors[import_match.get_end("path") + 1] = {"color": text_edit.theme_overrides.conditions_color}
|
||||
colors[import_match.get_start("prefix")] = {"color": text_edit.theme_overrides.members_color}
|
||||
colors[import_match.get_end("prefix")] = {"color": text_edit.theme_overrides.conditions_color}
|
||||
DMConstants.TYPE_IMPORT:
|
||||
colors[index] = { color = theme.conditions_color }
|
||||
var import: RegExMatch = regex.IMPORT_REGEX.search(text)
|
||||
colors[index + import.get_start("path") - 1] = { color = theme.strings_color }
|
||||
colors[index + import.get_end("path") + 1] = { color = theme.conditions_color }
|
||||
colors[index + import.get_start("prefix")] = { color = theme.text_color }
|
||||
|
||||
# Using clauses
|
||||
var using_matches: Array[RegExMatch] = dialogue_manager_parser.USING_REGEX.search_all(text)
|
||||
for using_match in using_matches:
|
||||
colors[using_match.get_start(0)] = {"color": text_edit.theme_overrides.conditions_color}
|
||||
colors[using_match.get_start("state") - 1] = {"color": text_edit.theme_overrides.text_color}
|
||||
DMConstants.TYPE_COMMENT:
|
||||
colors[index] = { color = theme.comments_color }
|
||||
|
||||
# Condition keywords and expressions
|
||||
var condition_matches: Array[RegExMatch] = regex_condition.search_all(text)
|
||||
for condition_match in condition_matches:
|
||||
colors[condition_match.get_start(0)] = {"color": text_edit.theme_overrides.conditions_color}
|
||||
colors[condition_match.get_end(1)] = {"color": text_edit.theme_overrides.text_color}
|
||||
colors.merge(_get_expression_syntax_highlighting(condition_match.get_start("condition"), ExpressionType.IF, condition_match.get_string("condition")), true)
|
||||
# endif/else
|
||||
var endcondition_matches: Array[RegExMatch] = regex_endconditions.search_all(text)
|
||||
for endcondition_match in endcondition_matches:
|
||||
colors[endcondition_match.get_start(1)] = {"color": text_edit.theme_overrides.conditions_color}
|
||||
colors[endcondition_match.get_end(1)] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
DMConstants.TYPE_TITLE:
|
||||
colors[index] = { color = theme.titles_color }
|
||||
|
||||
# Mutations
|
||||
var mutation_matches: Array[RegExMatch] = regex_mutation.search_all(text)
|
||||
for mutation_match in mutation_matches:
|
||||
colors[mutation_match.get_start(0)] = {"color": text_edit.theme_overrides.mutations_color}
|
||||
colors.merge(_get_expression_syntax_highlighting(mutation_match.get_start("mutation"), ExpressionType.DO if mutation_match.strings[1] == "do" else ExpressionType.SET, mutation_match.get_string("mutation")), true)
|
||||
DMConstants.TYPE_CONDITION, DMConstants.TYPE_WHILE, DMConstants.TYPE_MATCH, DMConstants.TYPE_WHEN:
|
||||
colors[0] = { color = theme.conditions_color }
|
||||
index = text.find(" ")
|
||||
if index > -1:
|
||||
var expression: Array = expression_parser.tokenise(text.substr(index), DMConstants.TYPE_CONDITION, 0)
|
||||
if expression.size() == 0 or expression[0].type == DMConstants.TYPE_ERROR:
|
||||
colors[index] = { color = theme.critical_color }
|
||||
else:
|
||||
_highlight_expression(expression, colors, index)
|
||||
|
||||
DMConstants.TYPE_MUTATION:
|
||||
colors[0] = { color = theme.mutations_color }
|
||||
index = text.find(" ")
|
||||
var expression: Array = expression_parser.tokenise(text.substr(index), DMConstants.TYPE_MUTATION, 0)
|
||||
if expression.size() == 0 or expression[0].type == DMConstants.TYPE_ERROR:
|
||||
colors[index] = { color = theme.critical_color }
|
||||
else:
|
||||
_highlight_expression(expression, colors, index)
|
||||
|
||||
DMConstants.TYPE_GOTO:
|
||||
if text.strip_edges().begins_with("%"):
|
||||
colors[index] = { color = theme.symbols_color }
|
||||
index = text.find(" ")
|
||||
_highlight_goto(text, colors, index)
|
||||
|
||||
DMConstants.TYPE_RANDOM:
|
||||
colors[index] = { color = theme.symbols_color }
|
||||
|
||||
DMConstants.TYPE_DIALOGUE, DMConstants.TYPE_RESPONSE:
|
||||
if text.strip_edges().begins_with("%"):
|
||||
colors[index] = { color = theme.symbols_color }
|
||||
index = text.find(" ", text.find("%"))
|
||||
colors[index] = { color = theme.text_color.lerp(theme.symbols_color, 0.5) }
|
||||
|
||||
var dialogue_text: String = text.substr(index, text.find("=>"))
|
||||
|
||||
# Highlight character name
|
||||
var split_index: int = dialogue_text.replace("\\:", "??").find(":")
|
||||
colors[index + split_index + 1] = { color = theme.text_color }
|
||||
|
||||
# Interpolation
|
||||
var replacements: Array[RegExMatch] = regex.REPLACEMENTS_REGEX.search_all(dialogue_text)
|
||||
for replacement: RegExMatch in replacements:
|
||||
var expression_text: String = replacement.get_string().substr(0, replacement.get_string().length() - 2).substr(2)
|
||||
var expression: Array = expression_parser.tokenise(expression_text, DMConstants.TYPE_MUTATION, replacement.get_start())
|
||||
var expression_index: int = index + replacement.get_start()
|
||||
colors[expression_index] = { color = theme.symbols_color }
|
||||
if expression.size() == 0 or expression[0].type == DMConstants.TYPE_ERROR:
|
||||
colors[expression_index] = { color = theme.critical_color }
|
||||
else:
|
||||
_highlight_expression(expression, colors, index + 2)
|
||||
colors[expression_index + expression_text.length() + 2] = { color = theme.symbols_color }
|
||||
colors[expression_index + expression_text.length() + 4] = { color = theme.text_color }
|
||||
# Tags (and inline mutations)
|
||||
var resolved_line_data: DMResolvedLineData = DMResolvedLineData.new("")
|
||||
var bbcodes: Array[Dictionary] = resolved_line_data.find_bbcode_positions_in_string(dialogue_text, true, true)
|
||||
for bbcode: Dictionary in bbcodes:
|
||||
var tag: String = bbcode.code
|
||||
var code: String = bbcode.raw_args
|
||||
if code.begins_with("["):
|
||||
colors[index + bbcode.start] = { color = theme.symbols_color }
|
||||
colors[index + bbcode.start + 2] = { color = theme.text_color }
|
||||
var pipe_cursor: int = code.find("|")
|
||||
while pipe_cursor > -1:
|
||||
colors[index + bbcode.start + pipe_cursor + 1] = { color = theme.symbols_color }
|
||||
colors[index + bbcode.start + pipe_cursor + 2] = { color = theme.text_color }
|
||||
pipe_cursor = code.find("|", pipe_cursor + 1)
|
||||
colors[index + bbcode.end - 1] = { color = theme.symbols_color }
|
||||
colors[index + bbcode.end + 1] = { color = theme.text_color }
|
||||
else:
|
||||
colors[index + bbcode.start] = { color = theme.symbols_color }
|
||||
if tag.begins_with("do") or tag.begins_with("set") or tag.begins_with("if"):
|
||||
if tag.begins_with("if"):
|
||||
colors[index + bbcode.start + 1] = { color = theme.conditions_color }
|
||||
else:
|
||||
colors[index + bbcode.start + 1] = { color = theme.mutations_color }
|
||||
var expression: Array = expression_parser.tokenise(code, DMConstants.TYPE_MUTATION, bbcode.start + bbcode.code.length())
|
||||
if expression.size() == 0 or expression[0].type == DMConstants.TYPE_ERROR:
|
||||
colors[index + bbcode.start + tag.length() + 1] = { color = theme.critical_color }
|
||||
else:
|
||||
_highlight_expression(expression, colors, index + 2)
|
||||
# else and closing if have no expression
|
||||
elif tag.begins_with("else") or tag.begins_with("/if"):
|
||||
colors[index + bbcode.start + 1] = { color = theme.conditions_color }
|
||||
colors[index + bbcode.end] = { color = theme.symbols_color }
|
||||
colors[index + bbcode.end + 1] = { color = theme.text_color }
|
||||
# Jumps
|
||||
if "=> " in text or "=>< " in text:
|
||||
_highlight_goto(text, colors, index)
|
||||
|
||||
# Order the dictionary keys to prevent CodeEdit from having issues
|
||||
var new_colors: Dictionary = {}
|
||||
var ordered_colors: Dictionary = {}
|
||||
var ordered_keys: Array = colors.keys()
|
||||
ordered_keys.sort()
|
||||
for index in ordered_keys:
|
||||
new_colors[index] = colors[index]
|
||||
for key_index: int in ordered_keys:
|
||||
ordered_colors[key_index] = colors[key_index]
|
||||
|
||||
cache[text] = new_colors
|
||||
return new_colors
|
||||
cache[text] = ordered_colors
|
||||
return ordered_colors
|
||||
|
||||
|
||||
## Return the syntax highlighting for a dialogue line
|
||||
func _get_dialogue_syntax_highlighting(start_index: int, text: String) -> Dictionary:
|
||||
var text_edit: TextEdit = get_text_edit()
|
||||
var colors: Dictionary = {}
|
||||
func _highlight_expression(tokens: Array, colors: Dictionary, index: int) -> int:
|
||||
var theme: Dictionary = get_text_edit().theme_overrides
|
||||
var last_index: int = index
|
||||
for token: Dictionary in tokens:
|
||||
last_index = token.i
|
||||
match token.type:
|
||||
DMConstants.TOKEN_CONDITION, DMConstants.TOKEN_AND_OR:
|
||||
colors[index + token.i] = { color = theme.conditions_color }
|
||||
|
||||
# #tag style tags
|
||||
var hashtag_matches: Array[RegExMatch] = dialogue_manager_parser.TAGS_REGEX.search_all(text)
|
||||
for hashtag_match in hashtag_matches:
|
||||
colors[start_index + hashtag_match.get_start(0)] = { "color": text_edit.theme_overrides.comments_color }
|
||||
colors[start_index + hashtag_match.get_end(0)] = { "color": text_edit.theme_overrides.text_color }
|
||||
|
||||
# bbcode-like global tags
|
||||
var tag_matches: Array[RegExMatch] = regex_tags.search_all(text)
|
||||
for tag_match in tag_matches:
|
||||
colors[start_index + tag_match.get_start(0)] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
if "val" in tag_match.names:
|
||||
colors.merge(_get_literal_syntax_highlighting(start_index + tag_match.get_start("val"), tag_match.get_string("val")), true)
|
||||
colors[start_index + tag_match.get_end("val")] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
# Show the text color straight in the editor for better ease-of-use
|
||||
if tag_match.get_string("tag") == "color":
|
||||
colors[start_index + tag_match.get_start("val")] = {"color": Color.from_string(tag_match.get_string("val"), text_edit.theme_overrides.text_color)}
|
||||
if "text" in tag_match.names:
|
||||
colors[start_index + tag_match.get_start("text")] = {"color": text_edit.theme_overrides.text_color}
|
||||
# Text can still contain tags if several effects are applied ([center][b]Something[/b][/center], so recursing
|
||||
colors.merge(_get_dialogue_syntax_highlighting(start_index + tag_match.get_start("text"), tag_match.get_string("text")), true)
|
||||
colors[start_index + tag_match.get_end("text")] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
if "end" in tag_match.names:
|
||||
colors[start_index + tag_match.get_start("end")] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
colors[start_index + tag_match.get_end("end")] = {"color": text_edit.theme_overrides.text_color}
|
||||
colors[start_index + tag_match.get_end(0)] = {"color": text_edit.theme_overrides.text_color}
|
||||
|
||||
# ID tag
|
||||
var translation_matches: Array[RegExMatch] = dialogue_manager_parser.TRANSLATION_REGEX.search_all(text)
|
||||
for translation_match in translation_matches:
|
||||
colors[start_index + translation_match.get_start(0)] = {"color": text_edit.theme_overrides.comments_color}
|
||||
colors[start_index + translation_match.get_end(0)] = {"color": text_edit.theme_overrides.text_color}
|
||||
|
||||
# Replacements
|
||||
var replacement_matches: Array[RegExMatch] = dialogue_manager_parser.REPLACEMENTS_REGEX.search_all(text)
|
||||
for replacement_match in replacement_matches:
|
||||
colors[start_index + replacement_match.get_start(0)] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
colors[start_index + replacement_match.get_start(1)] = {"color": text_edit.theme_overrides.text_color}
|
||||
colors.merge(_get_literal_syntax_highlighting(start_index + replacement_match.get_start(1), replacement_match.strings[1]), true)
|
||||
colors[start_index + replacement_match.get_end(1)] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
colors[start_index + replacement_match.get_end(0)] = {"color": text_edit.theme_overrides.text_color}
|
||||
|
||||
# Jump at the end of a response
|
||||
var goto_matches: Array[RegExMatch] = regex_goto.search_all(text)
|
||||
for goto_match in goto_matches:
|
||||
colors[start_index + goto_match.get_start(0)] = {"color": text_edit.theme_overrides.jumps_color}
|
||||
if "file" in goto_match.names:
|
||||
colors[start_index + goto_match.get_start("file")] = {"color": text_edit.theme_overrides.jumps_color}
|
||||
colors[start_index + goto_match.get_end("file")] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
colors[start_index + goto_match.get_start("title")] = {"color": text_edit.theme_overrides.jumps_color}
|
||||
colors[start_index + goto_match.get_end("title")] = {"color": text_edit.theme_overrides.jumps_color}
|
||||
colors[start_index + goto_match.get_end(0)] = {"color": text_edit.theme_overrides.text_color}
|
||||
|
||||
# Wrapped condition
|
||||
var wcondition_matches: Array[RegExMatch] = regex_wcondition.search_all(text)
|
||||
for wcondition_match in wcondition_matches:
|
||||
colors[start_index + wcondition_match.get_start(0)] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
colors[start_index + wcondition_match.get_start(0) + 1] = {"color": text_edit.theme_overrides.conditions_color}
|
||||
colors[start_index + wcondition_match.get_start(0) + 3] = {"color": text_edit.theme_overrides.text_color}
|
||||
colors.merge(_get_literal_syntax_highlighting(start_index + wcondition_match.get_start("condition"), wcondition_match.get_string("condition")), true)
|
||||
colors[start_index + wcondition_match.get_end("condition")] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
colors[start_index + wcondition_match.get_end(0)] = {"color": text_edit.theme_overrides.text_color}
|
||||
# [/if] tag for color matching with the opening tag
|
||||
var wendif_matches: Array[RegExMatch] = regex_wendif.search_all(text)
|
||||
for wendif_match in wendif_matches:
|
||||
colors[start_index + wendif_match.get_start(0)] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
colors[start_index + wendif_match.get_start(1)] = {"color": text_edit.theme_overrides.conditions_color}
|
||||
colors[start_index + wendif_match.get_end(1)] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
colors[start_index + wendif_match.get_end(0)] = {"color": text_edit.theme_overrides.text_color}
|
||||
|
||||
# Random groups
|
||||
var rgroup_matches: Array[RegExMatch] = regex_rgroup.search_all(text)
|
||||
for rgroup_match in rgroup_matches:
|
||||
colors[start_index + rgroup_match.get_start(0)] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
colors[start_index + rgroup_match.get_start("options")] = {"color": text_edit.theme_overrides.text_color}
|
||||
var separator_matches: Array[RegExMatch] = RegEx.create_from_string("\\|").search_all(rgroup_match.get_string("options"))
|
||||
for separator_match in separator_matches:
|
||||
colors[start_index + rgroup_match.get_start("options") + separator_match.get_start(0)] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
colors[start_index + rgroup_match.get_start("options") + separator_match.get_end(0)] = {"color": text_edit.theme_overrides.text_color}
|
||||
colors[start_index + rgroup_match.get_end("options")] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
colors[start_index + rgroup_match.get_end(0)] = {"color": text_edit.theme_overrides.text_color}
|
||||
|
||||
return colors
|
||||
|
||||
|
||||
## Returns the syntax highlighting for an expression (mutation set/do, or condition)
|
||||
func _get_expression_syntax_highlighting(start_index: int, type: ExpressionType, text: String) -> Dictionary:
|
||||
var text_edit: TextEdit = get_text_edit()
|
||||
var colors: Dictionary = {}
|
||||
|
||||
if type == ExpressionType.SET:
|
||||
var assignment_matches: Array[RegExMatch] = regex_assignment.search_all(text)
|
||||
for assignment_match in assignment_matches:
|
||||
colors[start_index + assignment_match.get_start("var")] = {"color": text_edit.theme_overrides.text_color}
|
||||
if "attr" in assignment_match.names:
|
||||
colors[start_index + assignment_match.get_start("attr")] = {"color": text_edit.theme_overrides.members_color}
|
||||
colors[start_index + assignment_match.get_end("attr")] = {"color": text_edit.theme_overrides.text_color}
|
||||
if "key" in assignment_match.names:
|
||||
# Braces are outside of the key, so coloring them symbols_color
|
||||
colors[start_index + assignment_match.get_start("key") - 1] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
colors.merge(_get_literal_syntax_highlighting(start_index + assignment_match.get_start("key"), assignment_match.get_string("key")), true)
|
||||
colors[start_index + assignment_match.get_end("key")] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
colors[start_index + assignment_match.get_end("key") + 1] = {"color": text_edit.theme_overrides.text_color}
|
||||
|
||||
colors[start_index + assignment_match.get_start("op")] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
colors[start_index + assignment_match.get_end("op")] = {"color": text_edit.theme_overrides.text_color}
|
||||
colors.merge(_get_literal_syntax_highlighting(start_index + assignment_match.get_start("val"), assignment_match.get_string("val")), true)
|
||||
DMConstants.TOKEN_VARIABLE:
|
||||
if token.value in ["true", "false"]:
|
||||
colors[index + token.i] = { color = theme.conditions_color }
|
||||
else:
|
||||
colors.merge(_get_literal_syntax_highlighting(start_index, text), true)
|
||||
colors[index + token.i] = { color = theme.members_color }
|
||||
|
||||
return colors
|
||||
DMConstants.TOKEN_OPERATOR, DMConstants.TOKEN_COLON, DMConstants.TOKEN_COMMA, DMConstants.TOKEN_NUMBER, DMConstants.TOKEN_ASSIGNMENT:
|
||||
colors[index + token.i] = { color = theme.symbols_color }
|
||||
|
||||
DMConstants.TOKEN_STRING:
|
||||
colors[index + token.i] = { color = theme.strings_color }
|
||||
|
||||
DMConstants.TOKEN_FUNCTION:
|
||||
colors[index + token.i] = { color = theme.mutations_color }
|
||||
colors[index + token.i + token.function.length()] = { color = theme.symbols_color }
|
||||
for parameter: Array in token.value:
|
||||
last_index = _highlight_expression(parameter, colors, index)
|
||||
DMConstants.TOKEN_PARENS_CLOSE:
|
||||
colors[index + token.i] = { color = theme.symbols_color }
|
||||
|
||||
DMConstants.TOKEN_DICTIONARY_REFERENCE:
|
||||
colors[index + token.i] = { color = theme.members_color }
|
||||
colors[index + token.i + token.variable.length()] = { color = theme.symbols_color }
|
||||
last_index = _highlight_expression(token.value, colors, index)
|
||||
DMConstants.TOKEN_ARRAY:
|
||||
colors[index + token.i] = { color = theme.symbols_color }
|
||||
for item: Array in token.value:
|
||||
last_index = _highlight_expression(item, colors, index)
|
||||
DMConstants.TOKEN_BRACKET_CLOSE:
|
||||
colors[index + token.i] = { color = theme.symbols_color }
|
||||
|
||||
DMConstants.TOKEN_DICTIONARY:
|
||||
colors[index + token.i] = { color = theme.symbols_color }
|
||||
last_index = _highlight_expression(token.value.keys() + token.value.values(), colors, index)
|
||||
DMConstants.TOKEN_BRACE_CLOSE:
|
||||
colors[index + token.i] = { color = theme.symbols_color }
|
||||
last_index += 1
|
||||
|
||||
DMConstants.TOKEN_GROUP:
|
||||
last_index = _highlight_expression(token.value, colors, index)
|
||||
|
||||
return last_index
|
||||
|
||||
|
||||
## Return the syntax highlighting for a literal
|
||||
## For this purpose, "literal" refers to a regular code line that could be used to get a value out of:
|
||||
## - function calls
|
||||
## - real literals (bool, string, int, float, etc.)
|
||||
## - logical operators (>, <, >=, or, and, not, etc.)
|
||||
func _get_literal_syntax_highlighting(start_index: int, text: String) -> Dictionary:
|
||||
var text_edit: TextEdit = get_text_edit()
|
||||
var colors: Dictionary = {}
|
||||
func _highlight_goto(text: String, colors: Dictionary, index: int) -> int:
|
||||
var theme: Dictionary = get_text_edit().theme_overrides
|
||||
var goto_data: DMResolvedGotoData = DMResolvedGotoData.new(text, {})
|
||||
colors[goto_data.index] = { color = theme.jumps_color }
|
||||
if "{{" in text:
|
||||
index = text.find("{{", goto_data.index)
|
||||
var last_index: int = 0
|
||||
if goto_data.error:
|
||||
colors[index + 2] = { color = theme.critical_color }
|
||||
else:
|
||||
last_index = _highlight_expression(goto_data.expression, colors, index)
|
||||
index = text.find("}}", index + last_index)
|
||||
colors[index] = { color = theme.jumps_color }
|
||||
|
||||
# Remove spaces at start/end of the literal
|
||||
var text_length: int = text.length()
|
||||
text = text.lstrip(" ")
|
||||
start_index += text_length - text.length()
|
||||
text = text.rstrip(" ")
|
||||
|
||||
# Parenthesis expression.
|
||||
var paren_matches: Array[RegExMatch] = regex_paren.search_all(text)
|
||||
for paren_match in paren_matches:
|
||||
colors[start_index + paren_match.get_start(0)] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
colors[start_index + paren_match.get_start(0) + 1] = {"color": text_edit.theme_overrides.text_color}
|
||||
colors.merge(_get_literal_syntax_highlighting(start_index + paren_match.get_start("paren"), paren_match.get_string("paren")), true)
|
||||
colors[start_index + paren_match.get_end(0) - 1] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
|
||||
# Strings
|
||||
var string_matches: Array[RegExMatch] = regex_string.search_all(text)
|
||||
for string_match in string_matches:
|
||||
colors[start_index + string_match.get_start(0)] = {"color": text_edit.theme_overrides.strings_color}
|
||||
if "content" in string_match.names:
|
||||
var escape_matches: Array[RegExMatch] = regex_escape.search_all(string_match.get_string("content"))
|
||||
for escape_match in escape_matches:
|
||||
colors[start_index + string_match.get_start("content") + escape_match.get_start(0)] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
colors[start_index + string_match.get_start("content") + escape_match.get_end(0)] = {"color": text_edit.theme_overrides.strings_color}
|
||||
|
||||
# Numbers
|
||||
var number_matches: Array[RegExMatch] = regex_number.search_all(text)
|
||||
for number_match in number_matches:
|
||||
colors[start_index + number_match.get_start(0)] = {"color": text_edit.theme_overrides.numbers_color}
|
||||
|
||||
# Arrays
|
||||
var array_matches: Array[RegExMatch] = regex_array.search_all(text)
|
||||
for array_match in array_matches:
|
||||
colors[start_index + array_match.get_start(0)] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
colors.merge(_get_list_syntax_highlighting(start_index + array_match.get_start(1), array_match.strings[1]), true)
|
||||
colors[start_index + array_match.get_end(1)] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
|
||||
# Dictionaries
|
||||
var dict_matches: Array[RegExMatch] = regex_dict.search_all(text)
|
||||
for dict_match in dict_matches:
|
||||
colors[start_index + dict_match.get_start(0)] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
colors.merge(_get_list_syntax_highlighting(start_index + dict_match.get_start(1), dict_match.strings[1]), true)
|
||||
colors[start_index + dict_match.get_end(1)] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
|
||||
# Dictionary key: value pairs
|
||||
var kvdict_matches: Array[RegExMatch] = regex_kvdict.search_all(text)
|
||||
for kvdict_match in kvdict_matches:
|
||||
colors.merge(_get_literal_syntax_highlighting(start_index + kvdict_match.get_start("left"), kvdict_match.get_string("left")), true)
|
||||
colors[start_index + kvdict_match.get_start("colon")] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
colors[start_index + kvdict_match.get_end("colon")] = {"color": text_edit.theme_overrides.text_color}
|
||||
colors.merge(_get_literal_syntax_highlighting(start_index + kvdict_match.get_start("right"), kvdict_match.get_string("right")), true)
|
||||
|
||||
# Booleans
|
||||
var bool_matches: Array[RegExMatch] = regex_keyword.search_all(text)
|
||||
for bool_match in bool_matches:
|
||||
colors[start_index + bool_match.get_start(0)] = {"color": text_edit.theme_overrides.conditions_color}
|
||||
|
||||
# Functions
|
||||
var function_matches: Array[RegExMatch] = regex_function.search_all(text)
|
||||
for function_match in function_matches:
|
||||
var last_brace_index: int = text.rfind(")")
|
||||
colors[start_index + function_match.get_start(1)] = {"color": text_edit.theme_overrides.mutations_color}
|
||||
colors[start_index + function_match.get_end(1)] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
colors.merge(_get_list_syntax_highlighting(start_index + function_match.get_end(0), text.substr(function_match.get_end(0), last_brace_index - function_match.get_end(0))), true)
|
||||
colors[start_index + last_brace_index] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
|
||||
# Variables
|
||||
var varname_matches: Array[RegExMatch] = regex_varname.search_all(text)
|
||||
for varname_match in varname_matches:
|
||||
colors[start_index + varname_match.get_start("var")] = {"color": text_edit.theme_overrides.text_color}
|
||||
if "attr" in varname_match.names:
|
||||
colors[start_index + varname_match.get_start("attr")] = {"color": text_edit.theme_overrides.members_color}
|
||||
colors[start_index + varname_match.get_end("attr")] = {"color": text_edit.theme_overrides.text_color}
|
||||
if "key" in varname_match.names:
|
||||
# Braces are outside of the key, so coloring them symbols_color
|
||||
colors[start_index + varname_match.get_start("key") - 1] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
colors.merge(_get_literal_syntax_highlighting(start_index + varname_match.get_start("key"), varname_match.get_string("key")), true)
|
||||
colors[start_index + varname_match.get_end("key")] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
|
||||
# Comparison operators
|
||||
var comparison_matches: Array[RegExMatch] = regex_comparison.search_all(text)
|
||||
for comparison_match in comparison_matches:
|
||||
colors.merge(_get_literal_syntax_highlighting(start_index + comparison_match.get_start("left"), comparison_match.get_string("left")), true)
|
||||
colors[start_index + comparison_match.get_start("op")] = {"color": text_edit.theme_overrides.symbols_color}
|
||||
colors[start_index + comparison_match.get_end("op")] = {"color": text_edit.theme_overrides.text_color}
|
||||
var right = comparison_match.get_string("right")
|
||||
if right.ends_with(":"):
|
||||
right = right.substr(0, right.length() - 1)
|
||||
colors.merge(_get_literal_syntax_highlighting(start_index + comparison_match.get_start("right"), right), true)
|
||||
colors[start_index + comparison_match.get_start("right") + right.length()] = { "color": text_edit.theme_overrides.symbols_color }
|
||||
|
||||
# Logical binary operators
|
||||
var blogical_matches: Array[RegExMatch] = regex_blogical.search_all(text)
|
||||
for blogical_match in blogical_matches:
|
||||
colors.merge(_get_literal_syntax_highlighting(start_index + blogical_match.get_start("left"), blogical_match.get_string("left")), true)
|
||||
colors[start_index + blogical_match.get_start("op")] = {"color": text_edit.theme_overrides.conditions_color}
|
||||
colors[start_index + blogical_match.get_end("op")] = {"color": text_edit.theme_overrides.text_color}
|
||||
colors.merge(_get_literal_syntax_highlighting(start_index + blogical_match.get_start("right"), blogical_match.get_string("right")), true)
|
||||
|
||||
# Logical unary operators
|
||||
var ulogical_matches: Array[RegExMatch] = regex_ulogical.search_all(text)
|
||||
for ulogical_match in ulogical_matches:
|
||||
colors[start_index + ulogical_match.get_start("op")] = {"color": text_edit.theme_overrides.conditions_color}
|
||||
colors[start_index + ulogical_match.get_end("op")] = {"color": text_edit.theme_overrides.text_color}
|
||||
colors.merge(_get_literal_syntax_highlighting(start_index + ulogical_match.get_start("right"), ulogical_match.get_string("right")), true)
|
||||
|
||||
return colors
|
||||
|
||||
|
||||
## Returns the syntax coloring for a list of literals separated by commas
|
||||
func _get_list_syntax_highlighting(start_index: int, text: String) -> Dictionary:
|
||||
var text_edit: TextEdit = get_text_edit()
|
||||
var colors: Dictionary = {}
|
||||
|
||||
# Comma-separated list of literals (for arrays and function arguments)
|
||||
var element_matches: Array[RegExMatch] = regex_commas.search_all(text)
|
||||
for element_match in element_matches:
|
||||
colors.merge(_get_literal_syntax_highlighting(start_index + element_match.get_start(1), element_match.strings[1]), true)
|
||||
|
||||
return colors
|
||||
return index
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://c2pb8gpka0t0u
|
||||
uid://klpiq4tk3t7a
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
uid://cvqm0f453kjn1
|
||||
@@ -34,7 +34,7 @@ func _ready() -> void:
|
||||
|
||||
func _on_download_button_pressed() -> void:
|
||||
# Safeguard the actual dialogue manager repo from accidentally updating itself
|
||||
if FileAccess.file_exists("res://examples/test_scenes/test_scene.gd"):
|
||||
if FileAccess.file_exists("res://tests/test_basic_dialogue.gd"):
|
||||
prints("You can't update the addon from within itself.")
|
||||
failed.emit()
|
||||
return
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://dtgq7prk0yh50
|
||||
uid://kpwo418lb2t2
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://qdxrxv3c3hxk"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/dialogue_manager/components/download_update_panel.gd" id="1_4tm1k"]
|
||||
[ext_resource type="Script" uid="uid://kpwo418lb2t2" path="res://addons/dialogue_manager/components/download_update_panel.gd" id="1_4tm1k"]
|
||||
[ext_resource type="Texture2D" uid="uid://d3baj6rygkb3f" path="res://addons/dialogue_manager/assets/update.svg" id="2_4o2m6"]
|
||||
|
||||
[node name="DownloadUpdatePanel" type="Control"]
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://du1qxltygjocp
|
||||
uid://nyypeje1a036
|
||||
|
||||
@@ -63,7 +63,7 @@ func build_menu() -> void:
|
||||
func _on_new_dialog_file_selected(path: String) -> void:
|
||||
editor_plugin.main_view.new_file(path)
|
||||
is_waiting_for_file = false
|
||||
if Engine.get_meta("DialogueCache").has_file(path):
|
||||
if Engine.get_meta("DMCache").has_file(path):
|
||||
resource_changed.emit(load(path))
|
||||
else:
|
||||
var next_resource: Resource = await editor_plugin.import_plugin.compiled_resource
|
||||
@@ -81,7 +81,7 @@ func _on_file_dialog_canceled() -> void:
|
||||
|
||||
func _on_resource_button_pressed() -> void:
|
||||
if is_instance_valid(resource):
|
||||
editor_plugin.get_editor_interface().call_deferred("edit_resource", resource)
|
||||
EditorInterface.call_deferred("edit_resource", resource)
|
||||
else:
|
||||
build_menu()
|
||||
menu.position = get_viewport().position + Vector2i(
|
||||
@@ -112,7 +112,7 @@ func _on_menu_id_pressed(id: int) -> void:
|
||||
|
||||
ITEM_QUICK_LOAD:
|
||||
quick_selected_file = ""
|
||||
files_list.files = Engine.get_meta("DialogueCache").get_files()
|
||||
files_list.files = Engine.get_meta("DMCache").get_files()
|
||||
if resource:
|
||||
files_list.select_file(resource.resource_path)
|
||||
quick_open_dialog.popup_centered()
|
||||
@@ -123,13 +123,13 @@ func _on_menu_id_pressed(id: int) -> void:
|
||||
open_dialog.popup_centered()
|
||||
|
||||
ITEM_EDIT:
|
||||
editor_plugin.get_editor_interface().call_deferred("edit_resource", resource)
|
||||
EditorInterface.call_deferred("edit_resource", resource)
|
||||
|
||||
ITEM_CLEAR:
|
||||
resource_changed.emit(null)
|
||||
|
||||
ITEM_FILESYSTEM:
|
||||
var file_system = editor_plugin.get_editor_interface().get_file_system_dock()
|
||||
var file_system = EditorInterface.get_file_system_dock()
|
||||
file_system.navigate_to_path(resource.resource_path)
|
||||
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://d1wvrhfmr8ry6
|
||||
uid://dooe2pflnqtve
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://ycn6uaj7dsrh"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/dialogue_manager/components/editor_property/editor_property_control.gd" id="1_het12"]
|
||||
[ext_resource type="Script" uid="uid://dooe2pflnqtve" path="res://addons/dialogue_manager/components/editor_property/editor_property_control.gd" id="1_het12"]
|
||||
[ext_resource type="PackedScene" uid="uid://b16uuqjuof3n5" path="res://addons/dialogue_manager/components/editor_property/resource_button.tscn" id="2_hh3d4"]
|
||||
[ext_resource type="PackedScene" uid="uid://dnufpcdrreva3" path="res://addons/dialogue_manager/components/files_list.tscn" id="3_l8fp6"]
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://b8gxhsrredou6
|
||||
uid://damhqta55t67c
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://b16uuqjuof3n5"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/dialogue_manager/components/editor_property/resource_button.gd" id="1_7u2i7"]
|
||||
[ext_resource type="Script" uid="uid://damhqta55t67c" path="res://addons/dialogue_manager/components/editor_property/resource_button.gd" id="1_7u2i7"]
|
||||
|
||||
[node name="ResourceButton" type="Button"]
|
||||
offset_right = 8.0
|
||||
|
||||
@@ -59,7 +59,7 @@ func show_error() -> void:
|
||||
show()
|
||||
count_label.text = DialogueConstants.translate(&"n_of_n").format({ index = error_index + 1, total = errors.size() })
|
||||
var error = errors[error_index]
|
||||
error_button.text = DialogueConstants.translate(&"errors.line_and_message").format({ line = error.line_number + 1, column = error.column_number, message = DialogueConstants.get_error_message(error.error) })
|
||||
error_button.text = DialogueConstants.translate(&"errors.line_and_message").format({ line = error.line_number, column = error.column_number, message = DialogueConstants.get_error_message(error.error) })
|
||||
if error.has("external_error"):
|
||||
error_button.text += " " + DialogueConstants.get_error_message(error.external_error)
|
||||
|
||||
@@ -72,7 +72,7 @@ func _on_errors_panel_theme_changed() -> void:
|
||||
|
||||
|
||||
func _on_error_button_pressed() -> void:
|
||||
emit_signal("error_pressed", errors[error_index].line_number, errors[error_index].column_number)
|
||||
error_pressed.emit(errors[error_index].line_number, errors[error_index].column_number)
|
||||
|
||||
|
||||
func _on_previous_button_pressed() -> void:
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://bodru4vhssqjm
|
||||
uid://d2l8nlb6hhrfp
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://cs8pwrxr5vxix"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/dialogue_manager/components/errors_panel.gd" id="1_nfm3c"]
|
||||
[ext_resource type="Script" uid="uid://d2l8nlb6hhrfp" path="res://addons/dialogue_manager/components/errors_panel.gd" id="1_nfm3c"]
|
||||
|
||||
[sub_resource type="Image" id="Image_d2tnf"]
|
||||
[sub_resource type="Image" id="Image_w0gko"]
|
||||
data = {
|
||||
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 93, 93, 55, 255, 97, 97, 58, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 98, 98, 47, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 94, 94, 46, 255, 93, 93, 236, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||
"format": "RGBA8",
|
||||
@@ -12,7 +12,7 @@ data = {
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_s6fxl"]
|
||||
image = SubResource("Image_d2tnf")
|
||||
image = SubResource("Image_w0gko")
|
||||
|
||||
[node name="ErrorsPanel" type="HBoxContainer"]
|
||||
visible = false
|
||||
|
||||
@@ -21,6 +21,7 @@ const MODIFIED_SUFFIX = "(*)"
|
||||
var file_map: Dictionary = {}
|
||||
|
||||
var current_file_path: String = ""
|
||||
var last_selected_file_path: String = ""
|
||||
|
||||
var files: PackedStringArray = []:
|
||||
set(next_files):
|
||||
@@ -33,7 +34,7 @@ var files: PackedStringArray = []:
|
||||
|
||||
var unsaved_files: Array[String] = []
|
||||
|
||||
var filter: String:
|
||||
var filter: String = "":
|
||||
set(next_filter):
|
||||
filter = next_filter
|
||||
apply_filter()
|
||||
@@ -57,6 +58,7 @@ func select_file(file: String) -> void:
|
||||
var item_text = list.get_item_text(i).replace(MODIFIED_SUFFIX, "")
|
||||
if item_text == get_nice_file(file, item_text.count("/") + 1):
|
||||
list.select(i)
|
||||
last_selected_file_path = file
|
||||
|
||||
|
||||
func mark_file_as_unsaved(file: String, is_unsaved: bool) -> void:
|
||||
@@ -112,6 +114,8 @@ func apply_filter() -> void:
|
||||
func apply_theme() -> void:
|
||||
if is_instance_valid(filter_edit):
|
||||
filter_edit.right_icon = get_theme_icon("Search", "EditorIcons")
|
||||
if is_instance_valid(list):
|
||||
list.add_theme_stylebox_override("panel", get_theme_stylebox("panel", "Panel"))
|
||||
|
||||
|
||||
### Signals
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://cxxkkarhbbf07
|
||||
uid://dqa4a4wwoo0aa
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://dnufpcdrreva3"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/dialogue_manager/components/files_list.gd" id="1_cytii"]
|
||||
[ext_resource type="Script" uid="uid://dqa4a4wwoo0aa" path="res://addons/dialogue_manager/components/files_list.gd" id="1_cytii"]
|
||||
[ext_resource type="Texture2D" uid="uid://d3lr2uas6ax8v" path="res://addons/dialogue_manager/assets/icon.svg" id="2_3ijx1"]
|
||||
|
||||
[node name="FilesList" type="VBoxContainer"]
|
||||
@@ -9,6 +9,7 @@ anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_vertical = 3
|
||||
script = ExtResource("1_cytii")
|
||||
icon = ExtResource("2_3ijx1")
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ func find_in_files() -> Dictionary:
|
||||
var results: Dictionary = {}
|
||||
|
||||
var q: String = input.text
|
||||
var cache = Engine.get_meta("DialogueCache")
|
||||
var cache = Engine.get_meta("DMCache")
|
||||
var file: FileAccess
|
||||
for path in cache.get_files():
|
||||
var path_results: Array = []
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://xe50vmll2xq4
|
||||
uid://q368fmxxa8sd
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://0n7hwviyyly4"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/dialogue_manager/components/find_in_files.gd" id="1_3xicy"]
|
||||
[ext_resource type="Script" uid="uid://q368fmxxa8sd" path="res://addons/dialogue_manager/components/find_in_files.gd" id="1_3xicy"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_owohg"]
|
||||
bg_color = Color(0.266667, 0.278431, 0.352941, 0.243137)
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
class_name DialogueManagerParseResult extends RefCounted
|
||||
|
||||
var imported_paths: PackedStringArray = []
|
||||
var using_states: PackedStringArray = []
|
||||
var titles: Dictionary = {}
|
||||
var character_names: PackedStringArray = []
|
||||
var first_title: String = ""
|
||||
var lines: Dictionary = {}
|
||||
var errors: Array[Dictionary] = []
|
||||
var raw_text: String = ""
|
||||
@@ -1 +0,0 @@
|
||||
uid://nkbwbj4jt5h5
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
uid://kmwjxv2fbqfk
|
||||
@@ -1,15 +0,0 @@
|
||||
extends RefCounted
|
||||
|
||||
var text: String = ""
|
||||
var pauses: Dictionary = {}
|
||||
var speeds: Dictionary = {}
|
||||
var mutations: Array[Array] = []
|
||||
var time: String = ""
|
||||
|
||||
|
||||
func _init(data: Dictionary) -> void:
|
||||
text = data.text
|
||||
pauses = data.pauses
|
||||
speeds = data.speeds
|
||||
mutations = data.mutations
|
||||
time = data.time
|
||||
@@ -1 +0,0 @@
|
||||
uid://dubbguqmg0y3y
|
||||
@@ -1,10 +0,0 @@
|
||||
extends RefCounted
|
||||
|
||||
|
||||
var tags: PackedStringArray = []
|
||||
var line_without_tags: String = ""
|
||||
|
||||
|
||||
func _init(data: Dictionary) -> void:
|
||||
tags = data.tags
|
||||
line_without_tags = data.line_without_tags
|
||||
@@ -1 +0,0 @@
|
||||
uid://do3qcb4qvked0
|
||||
@@ -1 +1 @@
|
||||
uid://bs2b6vbxavpev
|
||||
uid://cijsmjkq21cdq
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://gr8nakpbrhby"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/dialogue_manager/components/search_and_replace.gd" id="1_8oj1f"]
|
||||
[ext_resource type="Script" uid="uid://cijsmjkq21cdq" path="res://addons/dialogue_manager/components/search_and_replace.gd" id="1_8oj1f"]
|
||||
|
||||
[node name="SearchAndReplace" type="VBoxContainer"]
|
||||
visible = false
|
||||
|
||||
@@ -48,6 +48,8 @@ func apply_filter() -> void:
|
||||
func apply_theme() -> void:
|
||||
if is_instance_valid(filter_edit):
|
||||
filter_edit.right_icon = get_theme_icon("Search", "EditorIcons")
|
||||
if is_instance_valid(list):
|
||||
list.add_theme_stylebox_override("panel", get_theme_stylebox("panel", "Panel"))
|
||||
|
||||
|
||||
### Signals
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://5t2mxrdh0xm3
|
||||
uid://d0k2wndjj0ifm
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://ctns6ouwwd68i"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/dialogue_manager/components/title_list.gd" id="1_5qqmd"]
|
||||
[ext_resource type="Script" uid="uid://d0k2wndjj0ifm" path="res://addons/dialogue_manager/components/title_list.gd" id="1_5qqmd"]
|
||||
|
||||
[node name="TitleList" type="VBoxContainer"]
|
||||
anchors_preset = 15
|
||||
|
||||
@@ -86,9 +86,9 @@ func _on_update_button_pressed() -> void:
|
||||
if needs_reload:
|
||||
var will_refresh = on_before_refresh.call()
|
||||
if will_refresh:
|
||||
Engine.get_meta("DialogueManagerPlugin").get_editor_interface().restart_editor(true)
|
||||
EditorInterface.restart_editor(true)
|
||||
else:
|
||||
var scale: float = Engine.get_meta("DialogueManagerPlugin").get_editor_interface().get_editor_scale()
|
||||
var scale: float = EditorInterface.get_editor_scale()
|
||||
download_dialog.min_size = Vector2(300, 250) * scale
|
||||
download_dialog.popup_centered()
|
||||
|
||||
@@ -117,7 +117,7 @@ func _on_download_update_panel_failed() -> void:
|
||||
|
||||
|
||||
func _on_needs_reload_dialog_confirmed() -> void:
|
||||
Engine.get_meta("DialogueManagerPlugin").get_editor_interface().restart_editor(true)
|
||||
EditorInterface.restart_editor(true)
|
||||
|
||||
|
||||
func _on_timer_timeout() -> void:
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://boqd8cx71f1af
|
||||
uid://cr1tt12dh5ecr
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://co8yl23idiwbi"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/dialogue_manager/components/update_button.gd" id="1_d2tpb"]
|
||||
[ext_resource type="Script" uid="uid://cr1tt12dh5ecr" path="res://addons/dialogue_manager/components/update_button.gd" id="1_d2tpb"]
|
||||
[ext_resource type="PackedScene" uid="uid://qdxrxv3c3hxk" path="res://addons/dialogue_manager/components/download_update_panel.tscn" id="2_iwm7r"]
|
||||
|
||||
[node name="UpdateButton" type="Button"]
|
||||
|
||||
@@ -1,9 +1,23 @@
|
||||
extends Node
|
||||
class_name DMConstants extends RefCounted
|
||||
|
||||
|
||||
const USER_CONFIG_PATH = "user://dialogue_manager_user_config.json"
|
||||
const CACHE_PATH = "user://dialogue_manager_cache.json"
|
||||
|
||||
|
||||
enum MutationBehaviour {
|
||||
Wait,
|
||||
DoNotWait,
|
||||
Skip
|
||||
}
|
||||
|
||||
enum TranslationSource {
|
||||
None,
|
||||
Guess,
|
||||
CSV,
|
||||
PO
|
||||
}
|
||||
|
||||
# Token types
|
||||
|
||||
const TOKEN_FUNCTION = &"function"
|
||||
@@ -33,21 +47,27 @@ const TOKEN_NUMBER = &"number"
|
||||
const TOKEN_VARIABLE = &"variable"
|
||||
const TOKEN_COMMENT = &"comment"
|
||||
|
||||
const TOKEN_VALUE = &"value"
|
||||
const TOKEN_ERROR = &"error"
|
||||
|
||||
# Line types
|
||||
|
||||
const TYPE_UNKNOWN = &"unknown"
|
||||
const TYPE_UNKNOWN = &""
|
||||
const TYPE_IMPORT = &"import"
|
||||
const TYPE_USING = &"using"
|
||||
const TYPE_COMMENT = &"comment"
|
||||
const TYPE_RESPONSE = &"response"
|
||||
const TYPE_TITLE = &"title"
|
||||
const TYPE_CONDITION = &"condition"
|
||||
const TYPE_WHILE = &"while"
|
||||
const TYPE_MATCH = &"match"
|
||||
const TYPE_WHEN = &"when"
|
||||
const TYPE_MUTATION = &"mutation"
|
||||
const TYPE_GOTO = &"goto"
|
||||
const TYPE_DIALOGUE = &"dialogue"
|
||||
const TYPE_RANDOM = &"random"
|
||||
const TYPE_ERROR = &"error"
|
||||
|
||||
const TYPE_ELSE = &"else"
|
||||
|
||||
# Line IDs
|
||||
|
||||
const ID_NULL = &""
|
||||
@@ -64,7 +84,6 @@ const ERR_FILE_ALREADY_IMPORTED = 101
|
||||
const ERR_DUPLICATE_IMPORT_NAME = 102
|
||||
const ERR_EMPTY_TITLE = 103
|
||||
const ERR_DUPLICATE_TITLE = 104
|
||||
const ERR_NESTED_TITLE = 105
|
||||
const ERR_TITLE_INVALID_CHARACTERS = 106
|
||||
const ERR_UNKNOWN_TITLE = 107
|
||||
const ERR_INVALID_TITLE_REFERENCE = 108
|
||||
@@ -95,6 +114,11 @@ const ERR_UNEXPECTED_VARIABLE = 132
|
||||
const ERR_INVALID_INDEX = 133
|
||||
const ERR_UNEXPECTED_ASSIGNMENT = 134
|
||||
const ERR_UNKNOWN_USING = 135
|
||||
const ERR_EXPECTED_WHEN_OR_ELSE = 136
|
||||
const ERR_ONLY_ONE_ELSE_ALLOWED = 137
|
||||
const ERR_WHEN_MUST_BELONG_TO_MATCH = 138
|
||||
const ERR_CONCURRENT_LINE_WITHOUT_ORIGIN = 139
|
||||
const ERR_GOTO_NOT_ALLOWED_ON_CONCURRECT_LINES = 140
|
||||
|
||||
|
||||
## Get the error message
|
||||
@@ -110,8 +134,6 @@ static func get_error_message(error: int) -> String:
|
||||
return translate(&"errors.empty_title")
|
||||
ERR_DUPLICATE_TITLE:
|
||||
return translate(&"errors.duplicate_title")
|
||||
ERR_NESTED_TITLE:
|
||||
return translate(&"errors.nested_title")
|
||||
ERR_TITLE_INVALID_CHARACTERS:
|
||||
return translate(&"errors.invalid_title_string")
|
||||
ERR_TITLE_BEGINS_WITH_NUMBER:
|
||||
@@ -172,14 +194,22 @@ static func get_error_message(error: int) -> String:
|
||||
return translate(&"errors.unexpected_assignment")
|
||||
ERR_UNKNOWN_USING:
|
||||
return translate(&"errors.unknown_using")
|
||||
ERR_EXPECTED_WHEN_OR_ELSE:
|
||||
return translate(&"errors.expected_when_or_else")
|
||||
ERR_ONLY_ONE_ELSE_ALLOWED:
|
||||
return translate(&"errors.only_one_else_allowed")
|
||||
ERR_WHEN_MUST_BELONG_TO_MATCH:
|
||||
return translate(&"errors.when_must_belong_to_match")
|
||||
ERR_CONCURRENT_LINE_WITHOUT_ORIGIN:
|
||||
return translate(&"errors.concurrent_line_without_origin")
|
||||
ERR_GOTO_NOT_ALLOWED_ON_CONCURRECT_LINES:
|
||||
return translate(&"errors.goto_not_allowed_on_concurrect_lines")
|
||||
|
||||
return translate(&"errors.unknown")
|
||||
|
||||
|
||||
static func translate(string: String) -> String:
|
||||
var temp_node = new()
|
||||
var base_path = temp_node.get_script().resource_path.get_base_dir()
|
||||
temp_node.free()
|
||||
var base_path = new().get_script().resource_path.get_base_dir()
|
||||
|
||||
var language: String = TranslationServer.get_tool_locale()
|
||||
var translations_path: String = "%s/l10n/%s.po" % [base_path, language]
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://c4hfjcd11f1l4
|
||||
uid://b1oarbmjtyesf
|
||||
|
||||
@@ -47,6 +47,7 @@ var dialogue_line:
|
||||
set(next_dialogue_line):
|
||||
dialogue_line = next_dialogue_line
|
||||
custom_minimum_size = Vector2.ZERO
|
||||
text = ""
|
||||
text = dialogue_line.text
|
||||
get:
|
||||
return dialogue_line
|
||||
@@ -186,12 +187,13 @@ func _mutate_inline_mutations(index: int) -> void:
|
||||
if inline_mutation[0] > index:
|
||||
return
|
||||
if inline_mutation[0] == index and not _already_mutated_indices.has(index):
|
||||
_already_mutated_indices.append(index)
|
||||
_is_awaiting_mutation = true
|
||||
# The DialogueManager can't be referenced directly here so we need to get it by its path
|
||||
await Engine.get_singleton("DialogueManager").mutate(inline_mutation[1], dialogue_line.extra_game_states, true)
|
||||
await Engine.get_singleton("DialogueManager")._mutate(inline_mutation[1], dialogue_line.extra_game_states, true)
|
||||
_is_awaiting_mutation = false
|
||||
|
||||
_already_mutated_indices.append(index)
|
||||
|
||||
|
||||
# Determine if the current autopause character at the cursor should qualify to pause typing.
|
||||
func _should_auto_pause() -> bool:
|
||||
@@ -210,7 +212,7 @@ func _should_auto_pause() -> bool:
|
||||
# Ignore "." if it's between two numbers
|
||||
if visible_characters > 3 and parsed_text[visible_characters - 1] == ".":
|
||||
var possible_number: String = parsed_text.substr(visible_characters - 2, 3)
|
||||
if str(float(possible_number)) == possible_number:
|
||||
if str(float(possible_number)).pad_decimals(1) == possible_number:
|
||||
return false
|
||||
|
||||
# Ignore "." if it's used in an abbreviation
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://c544a0pa1ckmf
|
||||
uid://g32um0mltv5d
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://ckvgyvclnwggo"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/dialogue_manager/dialogue_label.gd" id="1_cital"]
|
||||
[ext_resource type="Script" uid="uid://g32um0mltv5d" path="res://addons/dialogue_manager/dialogue_label.gd" id="1_cital"]
|
||||
|
||||
[node name="DialogueLabel" type="RichTextLabel"]
|
||||
anchors_preset = 10
|
||||
|
||||
@@ -2,14 +2,11 @@
|
||||
class_name DialogueLine extends RefCounted
|
||||
|
||||
|
||||
const _DialogueConstants = preload("./constants.gd")
|
||||
|
||||
|
||||
## The ID of this line
|
||||
var id: String
|
||||
|
||||
## The internal type of this dialogue object. One of [code]TYPE_DIALOGUE[/code] or [code]TYPE_MUTATION[/code]
|
||||
var type: String = _DialogueConstants.TYPE_DIALOGUE
|
||||
var type: String = DMConstants.TYPE_DIALOGUE
|
||||
|
||||
## The next line ID after this line.
|
||||
var next_id: String = ""
|
||||
@@ -41,6 +38,9 @@ var inline_mutations: Array[Array] = []
|
||||
## A list of responses attached to this line of dialogue.
|
||||
var responses: Array = []
|
||||
|
||||
## A list of lines that are spoken simultaneously with this one.
|
||||
var concurrent_lines: Array[DialogueLine] = []
|
||||
|
||||
## A list of any extra game states to check when resolving variables and mutations.
|
||||
var extra_game_states: Array = []
|
||||
|
||||
@@ -65,7 +65,7 @@ func _init(data: Dictionary = {}) -> void:
|
||||
extra_game_states = data.get("extra_game_states", [])
|
||||
|
||||
match type:
|
||||
_DialogueConstants.TYPE_DIALOGUE:
|
||||
DMConstants.TYPE_DIALOGUE:
|
||||
character = data.character
|
||||
character_replacements = data.get("character_replacements", [] as Array[Dictionary])
|
||||
text = data.text
|
||||
@@ -76,16 +76,17 @@ func _init(data: Dictionary = {}) -> void:
|
||||
inline_mutations = data.get("inline_mutations", [] as Array[Array])
|
||||
time = data.get("time", "")
|
||||
tags = data.get("tags", [])
|
||||
concurrent_lines = data.get("concurrent_lines", [] as Array[DialogueLine])
|
||||
|
||||
_DialogueConstants.TYPE_MUTATION:
|
||||
DMConstants.TYPE_MUTATION:
|
||||
mutation = data.mutation
|
||||
|
||||
|
||||
func _to_string() -> String:
|
||||
match type:
|
||||
_DialogueConstants.TYPE_DIALOGUE:
|
||||
DMConstants.TYPE_DIALOGUE:
|
||||
return "<DialogueLine character=\"%s\" text=\"%s\">" % [character, text]
|
||||
_DialogueConstants.TYPE_MUTATION:
|
||||
DMConstants.TYPE_MUTATION:
|
||||
return "<DialogueLine mutation>"
|
||||
return ""
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://cub47u83kehp
|
||||
uid://rhuq0eyf8ar2
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1 +1 @@
|
||||
uid://cst7cjlwxlxj3
|
||||
uid://c3rodes2l3gxb
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
uid://bq8fntgcwiosq
|
||||
@@ -5,7 +5,6 @@
|
||||
class_name DialogueResource extends Resource
|
||||
|
||||
|
||||
const _DialogueManager = preload("./dialogue_manager.gd")
|
||||
const DialogueLine = preload("./dialogue_line.gd")
|
||||
|
||||
## A list of state shortcuts
|
||||
@@ -30,7 +29,7 @@ const DialogueLine = preload("./dialogue_line.gd")
|
||||
## Get the next printable line of dialogue, starting from a referenced line ([code]title[/code] can
|
||||
## be a title string or a stringified line number). Runs any mutations along the way and then returns
|
||||
## the first dialogue line encountered.
|
||||
func get_next_dialogue_line(title: String, extra_game_states: Array = [], mutation_behaviour: _DialogueManager.MutationBehaviour = _DialogueManager.MutationBehaviour.Wait) -> DialogueLine:
|
||||
func get_next_dialogue_line(title: String = "", extra_game_states: Array = [], mutation_behaviour: DMConstants.MutationBehaviour = DMConstants.MutationBehaviour.Wait) -> DialogueLine:
|
||||
return await Engine.get_singleton("DialogueManager").get_next_dialogue_line(self, title, extra_game_states, mutation_behaviour)
|
||||
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://3we8jdve7k1n
|
||||
uid://dbs4435dsf3ry
|
||||
|
||||
@@ -2,14 +2,11 @@
|
||||
class_name DialogueResponse extends RefCounted
|
||||
|
||||
|
||||
const _DialogueConstants = preload("./constants.gd")
|
||||
|
||||
|
||||
## The ID of this response
|
||||
var id: String
|
||||
|
||||
## The internal type of this dialogue object, always set to [code]TYPE_RESPONSE[/code].
|
||||
var type: String = _DialogueConstants.TYPE_RESPONSE
|
||||
var type: String = DMConstants.TYPE_RESPONSE
|
||||
|
||||
## The next line ID to use if this response is selected by the player.
|
||||
var next_id: String = ""
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://5oenbic4tlvj
|
||||
uid://cm0xpfeywpqid
|
||||
|
||||
@@ -14,6 +14,9 @@ signal response_selected(response)
|
||||
## The action for accepting a response (is possibly overridden by parent dialogue balloon).
|
||||
@export var next_action: StringName = &""
|
||||
|
||||
## Hide any responses where [code]is_allowed[/code] is false
|
||||
@export var hide_failed_responses: bool = false
|
||||
|
||||
## The list of dialogue responses.
|
||||
var responses: Array = []:
|
||||
get:
|
||||
@@ -31,6 +34,8 @@ var responses: Array = []:
|
||||
# Add new items
|
||||
if responses.size() > 0:
|
||||
for response in responses:
|
||||
if hide_failed_responses and not response.is_allowed: continue
|
||||
|
||||
var item: Control
|
||||
if is_instance_valid(response_template):
|
||||
item = response_template.duplicate(DUPLICATE_GROUPS | DUPLICATE_SCRIPTS | DUPLICATE_SIGNALS)
|
||||
@@ -39,7 +44,7 @@ var responses: Array = []:
|
||||
item = Button.new()
|
||||
item.name = "Response%d" % get_child_count()
|
||||
if not response.is_allowed:
|
||||
item.name = String(item.name) + "Disallowed"
|
||||
item.name = item.name + &"Disallowed"
|
||||
item.disabled = true
|
||||
|
||||
# If the item has a response property then use that
|
||||
@@ -59,7 +64,9 @@ var responses: Array = []:
|
||||
func _ready() -> void:
|
||||
visibility_changed.connect(func():
|
||||
if visible and get_menu_items().size() > 0:
|
||||
get_menu_items()[0].grab_focus()
|
||||
var first_item: Control = get_menu_items()[0]
|
||||
if first_item.is_inside_tree():
|
||||
first_item.grab_focus()
|
||||
)
|
||||
|
||||
if is_instance_valid(response_template):
|
||||
@@ -77,11 +84,6 @@ func get_menu_items() -> Array:
|
||||
return items
|
||||
|
||||
|
||||
## [b]DEPRECATED[/b]. Do not use.
|
||||
func set_responses(next_responses: Array) -> void:
|
||||
self.responses = next_responses
|
||||
|
||||
|
||||
#region Internal
|
||||
|
||||
|
||||
@@ -130,11 +132,11 @@ func _on_response_mouse_entered(item: Control) -> void:
|
||||
func _on_response_gui_input(event: InputEvent, item: Control, response) -> void:
|
||||
if "Disallowed" in item.name: return
|
||||
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
if event is InputEventMouseButton and event.is_pressed() and event.button_index == MOUSE_BUTTON_LEFT:
|
||||
get_viewport().set_input_as_handled()
|
||||
response_selected.emit(response)
|
||||
elif event.is_action_pressed(&"ui_accept" if next_action.is_empty() else next_action) and item in get_menu_items():
|
||||
get_viewport().set_input_as_handled()
|
||||
response_selected.emit(response)
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://bb52rsfwhkxbn
|
||||
@@ -1,43 +1,52 @@
|
||||
extends EditorTranslationParserPlugin
|
||||
class_name DMTranslationParserPlugin extends EditorTranslationParserPlugin
|
||||
|
||||
|
||||
const DialogueConstants = preload("./constants.gd")
|
||||
const DialogueSettings = preload("./settings.gd")
|
||||
const DialogueManagerParser = preload("./components/parser.gd")
|
||||
const DialogueManagerParseResult = preload("./components/parse_result.gd")
|
||||
## Cached result of parsing a dialogue file.
|
||||
var data: DMCompilerResult
|
||||
## List of characters that were added.
|
||||
var translated_character_names: PackedStringArray = []
|
||||
var translated_lines: Array[Dictionary] = []
|
||||
|
||||
|
||||
func _parse_file(path: String, msgids: Array, msgids_context_plural: Array) -> void:
|
||||
func _parse_file(path: String) -> Array[PackedStringArray]:
|
||||
var msgs: Array[PackedStringArray] = []
|
||||
var file: FileAccess = FileAccess.open(path, FileAccess.READ)
|
||||
var text: String = file.get_as_text()
|
||||
|
||||
var data: DialogueManagerParseResult = DialogueManagerParser.parse_string(text, path)
|
||||
data = DMCompiler.compile_string(text, path)
|
||||
|
||||
var known_keys: PackedStringArray = PackedStringArray([])
|
||||
|
||||
# Add all character names if settings ask for it
|
||||
if DialogueSettings.get_setting("export_characters_in_translation", true):
|
||||
var character_names: PackedStringArray = data.character_names
|
||||
for character_name in character_names:
|
||||
if DMSettings.get_setting(DMSettings.INCLUDE_CHARACTERS_IN_TRANSLATABLE_STRINGS_LIST, true):
|
||||
translated_character_names = [] as Array[DialogueLine]
|
||||
for character_name: String in data.character_names:
|
||||
if character_name in known_keys: continue
|
||||
|
||||
known_keys.append(character_name)
|
||||
|
||||
msgids_context_plural.append([character_name.replace('"', '\\"'), "dialogue", ""])
|
||||
translated_character_names.append(character_name)
|
||||
msgs.append(PackedStringArray([character_name.replace('"', '\"'), "dialogue", "", DMConstants.translate("translation_plugin.character_name")]))
|
||||
|
||||
# Add all dialogue lines and responses
|
||||
var dialogue: Dictionary = data.lines
|
||||
for key in dialogue.keys():
|
||||
for key: String in dialogue.keys():
|
||||
var line: Dictionary = dialogue.get(key)
|
||||
|
||||
if not line.type in [DialogueConstants.TYPE_DIALOGUE, DialogueConstants.TYPE_RESPONSE]: continue
|
||||
if line.translation_key in known_keys: continue
|
||||
if not line.type in [DMConstants.TYPE_DIALOGUE, DMConstants.TYPE_RESPONSE]: continue
|
||||
|
||||
known_keys.append(line.translation_key)
|
||||
var translation_key: String = line.get(&"translation_key", line.text)
|
||||
|
||||
if line.translation_key == "" or line.translation_key == line.text:
|
||||
msgids_context_plural.append([line.text.replace('"', '\\"'), "", ""])
|
||||
if translation_key in known_keys: continue
|
||||
|
||||
known_keys.append(translation_key)
|
||||
translated_lines.append(line)
|
||||
if translation_key == line.text:
|
||||
msgs.append(PackedStringArray([line.text.replace('"', '\"'), "", "", line.get("notes", "")]))
|
||||
else:
|
||||
msgids_context_plural.append([line.text.replace('"', '\\"'), line.translation_key.replace('"', '\\"'), ""])
|
||||
msgs.append(PackedStringArray([line.text.replace('"', '\"'), line.translation_key.replace('"', '\"'), "", line.get("notes", "")]))
|
||||
|
||||
return msgs
|
||||
|
||||
|
||||
func _get_recognized_extensions() -> PackedStringArray:
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://cgib8ttuvr1o8
|
||||
uid://c6bya881h1egb
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
|
||||
namespace DialogueManagerRuntime;
|
||||
|
||||
public partial class ExampleBalloon : CanvasLayer
|
||||
namespace DialogueManagerRuntime
|
||||
{
|
||||
public partial class ExampleBalloon : CanvasLayer
|
||||
{
|
||||
[Export] public string NextAction = "ui_accept";
|
||||
[Export] public string SkipAction = "ui_cancel";
|
||||
|
||||
@@ -25,10 +25,6 @@ public partial class ExampleBalloon : CanvasLayer
|
||||
get => dialogueLine;
|
||||
set
|
||||
{
|
||||
isWaitingForInput = false;
|
||||
balloon.FocusMode = Control.FocusModeEnum.All;
|
||||
balloon.GrabFocus();
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
QueueFree();
|
||||
@@ -36,10 +32,12 @@ public partial class ExampleBalloon : CanvasLayer
|
||||
}
|
||||
|
||||
dialogueLine = value;
|
||||
UpdateDialogue();
|
||||
ApplyDialogueLine();
|
||||
}
|
||||
}
|
||||
|
||||
Timer MutationCooldown = new Timer();
|
||||
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
@@ -64,10 +62,8 @@ public partial class ExampleBalloon : CanvasLayer
|
||||
}
|
||||
}
|
||||
|
||||
if (!isWaitingForInput)
|
||||
return;
|
||||
if (dialogueLine.Responses.Count > 0)
|
||||
return;
|
||||
if (!isWaitingForInput) return;
|
||||
if (dialogueLine.Responses.Count > 0) return;
|
||||
|
||||
GetViewport().SetInputAsHandled();
|
||||
|
||||
@@ -90,6 +86,18 @@ public partial class ExampleBalloon : CanvasLayer
|
||||
Next(response.NextId);
|
||||
}));
|
||||
|
||||
|
||||
// Hide the balloon when a mutation is running
|
||||
MutationCooldown.Timeout += () =>
|
||||
{
|
||||
if (willHideBalloon)
|
||||
{
|
||||
willHideBalloon = false;
|
||||
balloon.Hide();
|
||||
}
|
||||
};
|
||||
AddChild(MutationCooldown);
|
||||
|
||||
DialogueManager.Mutated += OnMutated;
|
||||
}
|
||||
|
||||
@@ -106,9 +114,25 @@ public partial class ExampleBalloon : CanvasLayer
|
||||
GetViewport().SetInputAsHandled();
|
||||
}
|
||||
|
||||
|
||||
public override async void _Notification(int what)
|
||||
{
|
||||
// Detect a change of locale and update the current dialogue line to show the new language
|
||||
if (what == NotificationTranslationChanged && IsInstanceValid(dialogueLabel))
|
||||
{
|
||||
float visibleRatio = dialogueLabel.VisibleRatio;
|
||||
DialogueLine = await DialogueManager.GetNextDialogueLine(resource, DialogueLine.Id, temporaryGameStates);
|
||||
if (visibleRatio < 1.0f)
|
||||
{
|
||||
dialogueLabel.Call("skip_typing");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async void Start(Resource dialogueResource, string title, Array<Variant> extraGameStates = null)
|
||||
{
|
||||
temporaryGameStates = extraGameStates ?? new Array<Variant>();
|
||||
temporaryGameStates = new Array<Variant> { this } + (extraGameStates ?? new Array<Variant>());
|
||||
isWaitingForInput = false;
|
||||
resource = dialogueResource;
|
||||
|
||||
@@ -125,12 +149,13 @@ public partial class ExampleBalloon : CanvasLayer
|
||||
#region Helpers
|
||||
|
||||
|
||||
private async void UpdateDialogue()
|
||||
private async void ApplyDialogueLine()
|
||||
{
|
||||
if (!IsNodeReady())
|
||||
{
|
||||
await ToSignal(this, SignalName.Ready);
|
||||
}
|
||||
MutationCooldown.Stop();
|
||||
|
||||
isWaitingForInput = false;
|
||||
balloon.FocusMode = Control.FocusModeEnum.All;
|
||||
balloon.GrabFocus();
|
||||
|
||||
// Set up the character name
|
||||
characterLabel.Visible = !string.IsNullOrEmpty(dialogueLine.Character);
|
||||
@@ -189,16 +214,10 @@ public partial class ExampleBalloon : CanvasLayer
|
||||
{
|
||||
isWaitingForInput = false;
|
||||
willHideBalloon = true;
|
||||
GetTree().CreateTimer(0.1f).Timeout += () =>
|
||||
{
|
||||
if (willHideBalloon)
|
||||
{
|
||||
willHideBalloon = false;
|
||||
balloon.Hide();
|
||||
}
|
||||
};
|
||||
MutationCooldown.Start(0.1f);
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
uid://dac8psvf0vqvj
|
||||
uid://5b3w40kwakl3
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
extends CanvasLayer
|
||||
class_name DialogueManagerExampleBalloon extends CanvasLayer
|
||||
## A basic dialogue balloon for use with Dialogue Manager.
|
||||
|
||||
## The action to use for advancing the dialogue
|
||||
@export var next_action: StringName = &"ui_accept"
|
||||
@@ -6,11 +7,6 @@ extends CanvasLayer
|
||||
## The action to use to skip typing the dialogue
|
||||
@export var skip_action: StringName = &"ui_cancel"
|
||||
|
||||
@onready var balloon: Control = %Balloon
|
||||
@onready var character_label: RichTextLabel = %CharacterLabel
|
||||
@onready var dialogue_label: DialogueLabel = %DialogueLabel
|
||||
@onready var responses_menu: DialogueResponsesMenu = %ResponsesMenu
|
||||
|
||||
## The dialogue resource
|
||||
var resource: DialogueResource
|
||||
|
||||
@@ -23,59 +19,38 @@ var is_waiting_for_input: bool = false
|
||||
## See if we are running a long mutation and should hide the balloon
|
||||
var will_hide_balloon: bool = false
|
||||
|
||||
## A dictionary to store any ephemeral variables
|
||||
var locals: Dictionary = {}
|
||||
|
||||
var _locale: String = TranslationServer.get_locale()
|
||||
|
||||
## The current line
|
||||
var dialogue_line: DialogueLine:
|
||||
set(next_dialogue_line):
|
||||
is_waiting_for_input = false
|
||||
balloon.focus_mode = Control.FOCUS_ALL
|
||||
balloon.grab_focus()
|
||||
|
||||
# The dialogue has finished so close the balloon
|
||||
if not next_dialogue_line:
|
||||
queue_free()
|
||||
return
|
||||
|
||||
# If the node isn't ready yet then none of the labels will be ready yet either
|
||||
if not is_node_ready():
|
||||
await ready
|
||||
|
||||
dialogue_line = next_dialogue_line
|
||||
|
||||
character_label.visible = not dialogue_line.character.is_empty()
|
||||
character_label.text = tr(dialogue_line.character, "dialogue")
|
||||
|
||||
dialogue_label.hide()
|
||||
dialogue_label.dialogue_line = dialogue_line
|
||||
|
||||
responses_menu.hide()
|
||||
responses_menu.set_responses(dialogue_line.responses)
|
||||
|
||||
# Show our balloon
|
||||
balloon.show()
|
||||
will_hide_balloon = false
|
||||
|
||||
dialogue_label.show()
|
||||
if not dialogue_line.text.is_empty():
|
||||
dialogue_label.type_out()
|
||||
await dialogue_label.finished_typing
|
||||
|
||||
# Wait for input
|
||||
if dialogue_line.responses.size() > 0:
|
||||
balloon.focus_mode = Control.FOCUS_NONE
|
||||
responses_menu.show()
|
||||
elif dialogue_line.time != "":
|
||||
var time = dialogue_line.text.length() * 0.02 if dialogue_line.time == "auto" else dialogue_line.time.to_float()
|
||||
await get_tree().create_timer(time).timeout
|
||||
next(dialogue_line.next_id)
|
||||
set(value):
|
||||
if value:
|
||||
dialogue_line = value
|
||||
apply_dialogue_line()
|
||||
else:
|
||||
is_waiting_for_input = true
|
||||
balloon.focus_mode = Control.FOCUS_ALL
|
||||
balloon.grab_focus()
|
||||
# The dialogue has finished so close the balloon
|
||||
queue_free()
|
||||
get:
|
||||
return dialogue_line
|
||||
|
||||
## A cooldown timer for delaying the balloon hide when encountering a mutation.
|
||||
var mutation_cooldown: Timer = Timer.new()
|
||||
|
||||
## The base balloon anchor
|
||||
@onready var balloon: Control = %Balloon
|
||||
|
||||
## The label showing the name of the currently speaking character
|
||||
@onready var character_label: RichTextLabel = %CharacterLabel
|
||||
|
||||
## The label showing the currently spoken dialogue
|
||||
@onready var dialogue_label: DialogueLabel = %DialogueLabel
|
||||
|
||||
## The menu of responses
|
||||
@onready var responses_menu: DialogueResponsesMenu = %ResponsesMenu
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
balloon.hide()
|
||||
@@ -85,6 +60,9 @@ func _ready() -> void:
|
||||
if responses_menu.next_action.is_empty():
|
||||
responses_menu.next_action = next_action
|
||||
|
||||
mutation_cooldown.timeout.connect(_on_mutation_cooldown_timeout)
|
||||
add_child(mutation_cooldown)
|
||||
|
||||
|
||||
func _unhandled_input(_event: InputEvent) -> void:
|
||||
# Only the balloon is allowed to handle input while it's showing
|
||||
@@ -109,6 +87,46 @@ func start(dialogue_resource: DialogueResource, title: String, extra_game_states
|
||||
self.dialogue_line = await resource.get_next_dialogue_line(title, temporary_game_states)
|
||||
|
||||
|
||||
## Apply any changes to the balloon given a new [DialogueLine].
|
||||
func apply_dialogue_line() -> void:
|
||||
mutation_cooldown.stop()
|
||||
|
||||
is_waiting_for_input = false
|
||||
balloon.focus_mode = Control.FOCUS_ALL
|
||||
balloon.grab_focus()
|
||||
|
||||
character_label.visible = not dialogue_line.character.is_empty()
|
||||
character_label.text = tr(dialogue_line.character, "dialogue")
|
||||
|
||||
dialogue_label.hide()
|
||||
dialogue_label.dialogue_line = dialogue_line
|
||||
|
||||
responses_menu.hide()
|
||||
responses_menu.responses = dialogue_line.responses
|
||||
|
||||
# Show our balloon
|
||||
balloon.show()
|
||||
will_hide_balloon = false
|
||||
|
||||
dialogue_label.show()
|
||||
if not dialogue_line.text.is_empty():
|
||||
dialogue_label.type_out()
|
||||
await dialogue_label.finished_typing
|
||||
|
||||
# Wait for input
|
||||
if dialogue_line.responses.size() > 0:
|
||||
balloon.focus_mode = Control.FOCUS_NONE
|
||||
responses_menu.show()
|
||||
elif dialogue_line.time != "":
|
||||
var time = dialogue_line.text.length() * 0.02 if dialogue_line.time == "auto" else dialogue_line.time.to_float()
|
||||
await get_tree().create_timer(time).timeout
|
||||
next(dialogue_line.next_id)
|
||||
else:
|
||||
is_waiting_for_input = true
|
||||
balloon.focus_mode = Control.FOCUS_ALL
|
||||
balloon.grab_focus()
|
||||
|
||||
|
||||
## Go to the next line
|
||||
func next(next_id: String) -> void:
|
||||
self.dialogue_line = await resource.get_next_dialogue_line(next_id, temporary_game_states)
|
||||
@@ -117,14 +135,16 @@ func next(next_id: String) -> void:
|
||||
#region Signals
|
||||
|
||||
|
||||
func _on_mutated(_mutation: Dictionary) -> void:
|
||||
is_waiting_for_input = false
|
||||
will_hide_balloon = true
|
||||
get_tree().create_timer(0.1).timeout.connect(func():
|
||||
func _on_mutation_cooldown_timeout() -> void:
|
||||
if will_hide_balloon:
|
||||
will_hide_balloon = false
|
||||
balloon.hide()
|
||||
)
|
||||
|
||||
|
||||
func _on_mutated(_mutation: Dictionary) -> void:
|
||||
is_waiting_for_input = false
|
||||
will_hide_balloon = true
|
||||
mutation_cooldown.start(0.1)
|
||||
|
||||
|
||||
func _on_balloon_gui_input(event: InputEvent) -> void:
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://by1vx32y4g8gs
|
||||
uid://d1wt4ma6055l8
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[gd_scene load_steps=9 format=3 uid="uid://73jm5qjy52vq"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/dialogue_manager/example_balloon/example_balloon.gd" id="1_36de5"]
|
||||
[ext_resource type="Script" uid="uid://d1wt4ma6055l8" path="res://addons/dialogue_manager/example_balloon/example_balloon.gd" id="1_36de5"]
|
||||
[ext_resource type="PackedScene" uid="uid://ckvgyvclnwggo" path="res://addons/dialogue_manager/dialogue_label.tscn" id="2_a8ve6"]
|
||||
[ext_resource type="Script" path="res://addons/dialogue_manager/dialogue_reponses_menu.gd" id="3_72ixx"]
|
||||
[ext_resource type="Script" uid="uid://bb52rsfwhkxbn" path="res://addons/dialogue_manager/dialogue_responses_menu.gd" id="3_72ixx"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_spyqn"]
|
||||
bg_color = Color(0, 0, 0, 1)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[gd_scene load_steps=10 format=3 uid="uid://13s5spsk34qu"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/dialogue_manager/example_balloon/example_balloon.gd" id="1_s2gbs"]
|
||||
[ext_resource type="Script" uid="uid://d1wt4ma6055l8" path="res://addons/dialogue_manager/example_balloon/example_balloon.gd" id="1_s2gbs"]
|
||||
[ext_resource type="PackedScene" uid="uid://ckvgyvclnwggo" path="res://addons/dialogue_manager/dialogue_label.tscn" id="2_hfvdi"]
|
||||
[ext_resource type="Script" path="res://addons/dialogue_manager/dialogue_reponses_menu.gd" id="3_1j1j0"]
|
||||
[ext_resource type="Script" uid="uid://bb52rsfwhkxbn" path="res://addons/dialogue_manager/dialogue_responses_menu.gd" id="3_1j1j0"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_235ry"]
|
||||
content_margin_left = 6.0
|
||||
@@ -104,6 +104,7 @@ grow_vertical = 2
|
||||
theme = SubResource("Theme_qq3yp")
|
||||
|
||||
[node name="Panel" type="Panel" parent="Balloon"]
|
||||
clip_children = 2
|
||||
layout_mode = 1
|
||||
anchors_preset = 12
|
||||
anchor_top = 1.0
|
||||
@@ -115,6 +116,7 @@ offset_right = -4.0
|
||||
offset_bottom = -4.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 0
|
||||
mouse_filter = 1
|
||||
|
||||
[node name="Dialogue" type="MarginContainer" parent="Balloon/Panel"]
|
||||
layout_mode = 1
|
||||
@@ -142,7 +144,6 @@ unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
text = "Dialogue..."
|
||||
skip_pause_at_abbreviations = PackedStringArray("Mr", "Mrs", "Ms", "Dr", "etc", "eg", "ex")
|
||||
|
||||
[node name="Responses" type="MarginContainer" parent="Balloon"]
|
||||
layout_mode = 1
|
||||
|
||||
20
Zennysoft.Game.Ma/addons/dialogue_manager/export_plugin.gd
Normal file
20
Zennysoft.Game.Ma/addons/dialogue_manager/export_plugin.gd
Normal file
@@ -0,0 +1,20 @@
|
||||
class_name DMExportPlugin extends EditorExportPlugin
|
||||
|
||||
const IGNORED_PATHS = [
|
||||
"/assets",
|
||||
"/components",
|
||||
"/l10n",
|
||||
"/views"
|
||||
]
|
||||
|
||||
func _export_file(path: String, type: String, features: PackedStringArray) -> void:
|
||||
var plugin_path: String = Engine.get_meta("DialogueManagerPlugin").get_plugin_path()
|
||||
|
||||
# Ignore any editor stuff
|
||||
for ignored_path: String in IGNORED_PATHS:
|
||||
if path.begins_with(plugin_path + ignored_path):
|
||||
skip()
|
||||
|
||||
# Ignore C# stuff it not using dotnet
|
||||
if path.begins_with(plugin_path) and not DMSettings.check_for_dotnet_solution() and path.ends_with(".cs"):
|
||||
skip()
|
||||
@@ -0,0 +1 @@
|
||||
uid://sa55ra11ji2q
|
||||
@@ -1,20 +1,16 @@
|
||||
@tool
|
||||
extends EditorImportPlugin
|
||||
class_name DMImportPlugin extends EditorImportPlugin
|
||||
|
||||
|
||||
signal compiled_resource(resource: Resource)
|
||||
|
||||
|
||||
const DialogueResource = preload("./dialogue_resource.gd")
|
||||
const DialogueManagerParser = preload("./components/parser.gd")
|
||||
const DialogueManagerParseResult = preload("./components/parse_result.gd")
|
||||
|
||||
const compiler_version = 12
|
||||
const COMPILER_VERSION = 14
|
||||
|
||||
|
||||
func _get_importer_name() -> String:
|
||||
# NOTE: A change to this forces a re-import of all dialogue
|
||||
return "dialogue_manager_compiler_%s" % compiler_version
|
||||
return "dialogue_manager_compiler_%s" % COMPILER_VERSION
|
||||
|
||||
|
||||
func _get_visible_name() -> String:
|
||||
@@ -63,7 +59,7 @@ func _get_option_visibility(path: String, option_name: StringName, options: Dict
|
||||
|
||||
|
||||
func _import(source_file: String, save_path: String, options: Dictionary, platform_variants: Array[String], gen_files: Array[String]) -> Error:
|
||||
var cache = Engine.get_meta("DialogueCache")
|
||||
var cache = Engine.get_meta("DMCache")
|
||||
|
||||
# Get the raw file contents
|
||||
if not FileAccess.file_exists(source_file): return ERR_FILE_NOT_FOUND
|
||||
@@ -73,17 +69,12 @@ func _import(source_file: String, save_path: String, options: Dictionary, platfo
|
||||
|
||||
cache.file_content_changed.emit(source_file, raw_text)
|
||||
|
||||
# Parse the text
|
||||
var parser: DialogueManagerParser = DialogueManagerParser.new()
|
||||
var err: Error = parser.parse(raw_text, source_file)
|
||||
var data: DialogueManagerParseResult = parser.get_data()
|
||||
var errors: Array[Dictionary] = parser.get_errors()
|
||||
parser.free()
|
||||
|
||||
if err != OK:
|
||||
printerr("%d errors found in %s" % [errors.size(), source_file])
|
||||
cache.add_errors_to_file(source_file, errors)
|
||||
return err
|
||||
# Compile the text
|
||||
var result: DMCompilerResult = DMCompiler.compile_string(raw_text, source_file)
|
||||
if result.errors.size() > 0:
|
||||
printerr("%d errors found in %s" % [result.errors.size(), source_file])
|
||||
cache.add_errors_to_file(source_file, result.errors)
|
||||
return ERR_PARSE_ERROR
|
||||
|
||||
# Get the current addon version
|
||||
var config: ConfigFile = ConfigFile.new()
|
||||
@@ -94,17 +85,17 @@ func _import(source_file: String, save_path: String, options: Dictionary, platfo
|
||||
var resource: DialogueResource = DialogueResource.new()
|
||||
resource.set_meta("dialogue_manager_version", version)
|
||||
|
||||
resource.using_states = data.using_states
|
||||
resource.titles = data.titles
|
||||
resource.first_title = data.first_title
|
||||
resource.character_names = data.character_names
|
||||
resource.lines = data.lines
|
||||
resource.raw_text = data.raw_text
|
||||
resource.using_states = result.using_states
|
||||
resource.titles = result.titles
|
||||
resource.first_title = result.first_title
|
||||
resource.character_names = result.character_names
|
||||
resource.lines = result.lines
|
||||
resource.raw_text = result.raw_text
|
||||
|
||||
# Clear errors and possibly trigger any cascade recompiles
|
||||
cache.add_file(source_file, data)
|
||||
cache.add_file(source_file, result)
|
||||
|
||||
err = ResourceSaver.save(resource, "%s.%s" % [save_path, _get_save_extension()])
|
||||
var err: Error = ResourceSaver.save(resource, "%s.%s" % [save_path, _get_save_extension()])
|
||||
|
||||
compiled_resource.emit(resource)
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://db6mool8x8kfk
|
||||
uid://dhwpj6ed8soyq
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@tool
|
||||
extends EditorInspectorPlugin
|
||||
class_name DMInspectorPlugin extends EditorInspectorPlugin
|
||||
|
||||
|
||||
const DialogueEditorProperty = preload("./components/editor_property/editor_property.gd")
|
||||
|
||||
@@ -1 +1 @@
|
||||
uid://dsdontxsvnauk
|
||||
uid://0x31sbqbikov
|
||||
|
||||
@@ -21,6 +21,9 @@ msgstr "Open a file"
|
||||
msgid "open.open"
|
||||
msgstr "Open..."
|
||||
|
||||
msgid "open.quick_open"
|
||||
msgstr "Quick open..."
|
||||
|
||||
msgid "open.no_recent_files"
|
||||
msgstr "No recent files"
|
||||
|
||||
@@ -30,11 +33,17 @@ msgstr "Clear recent files"
|
||||
msgid "save_all_files"
|
||||
msgstr "Save all files"
|
||||
|
||||
msgid "all"
|
||||
msgstr "All"
|
||||
|
||||
msgid "find_in_files"
|
||||
msgstr "Find in files..."
|
||||
|
||||
msgid "test_dialogue"
|
||||
msgstr "Test dialogue"
|
||||
msgstr "Test dialogue from start of file"
|
||||
|
||||
msgid "test_dialogue_from_line"
|
||||
msgstr "Test dialogue from current line"
|
||||
|
||||
msgid "search_for_text"
|
||||
msgstr "Search for text"
|
||||
@@ -45,9 +54,6 @@ msgstr "Insert"
|
||||
msgid "translations"
|
||||
msgstr "Translations"
|
||||
|
||||
msgid "settings"
|
||||
msgstr "Settings"
|
||||
|
||||
msgid "sponsor"
|
||||
msgstr "Sponsor"
|
||||
|
||||
@@ -141,84 +147,6 @@ msgstr "Copy file path"
|
||||
msgid "buffer.show_in_filesystem"
|
||||
msgstr "Show in FileSystem"
|
||||
|
||||
msgid "settings.invalid_test_scene"
|
||||
msgstr "\"{path}\" does not extend BaseDialogueTestScene."
|
||||
|
||||
msgid "settings.revert_to_default_test_scene"
|
||||
msgstr "Revert to default test scene"
|
||||
|
||||
msgid "settings.default_balloon_hint"
|
||||
msgstr "Custom balloon to use when calling \"DialogueManager.show_balloon()\""
|
||||
|
||||
msgid "settings.revert_to_default_balloon"
|
||||
msgstr "Revert to default balloon"
|
||||
|
||||
msgid "settings.default_balloon_path"
|
||||
msgstr "<example balloon>"
|
||||
|
||||
msgid "settings.autoload"
|
||||
msgstr "Autoload"
|
||||
|
||||
msgid "settings.path"
|
||||
msgstr "Path"
|
||||
|
||||
msgid "settings.new_template"
|
||||
msgstr "New dialogue files will start with template text"
|
||||
|
||||
msgid "settings.missing_keys"
|
||||
msgstr "Treat missing translation keys as errors"
|
||||
|
||||
msgid "settings.missing_keys_hint"
|
||||
msgstr "If you are using static translation keys then having this enabled will help you find any lines that you haven't added a key to yet."
|
||||
|
||||
msgid "settings.characters_translations"
|
||||
msgstr "Export character names in translation files"
|
||||
|
||||
msgid "settings.wrap_long_lines"
|
||||
msgstr "Wrap long lines"
|
||||
|
||||
msgid "settings.include_failed_responses"
|
||||
msgstr "Include responses with failed conditions"
|
||||
|
||||
msgid "settings.ignore_missing_state_values"
|
||||
msgstr "Skip over missing state value errors (not recommended)"
|
||||
|
||||
msgid "settings.custom_test_scene"
|
||||
msgstr "Custom test scene (must extend BaseDialogueTestScene)"
|
||||
|
||||
msgid "settings.default_csv_locale"
|
||||
msgstr "Default CSV Locale"
|
||||
|
||||
msgid "settings.states_shortcuts"
|
||||
msgstr "State Shortcuts"
|
||||
|
||||
msgid "settings.states_message"
|
||||
msgstr "If an autoload is enabled here you can refer to its properties, methods, and signals without having to use its name."
|
||||
|
||||
msgid "settings.states_hint"
|
||||
msgstr "ie. Instead of \"SomeState.some_property\" you could just use \"some_property\""
|
||||
|
||||
msgid "settings.recompile_warning"
|
||||
msgstr "Changing these settings will force a recompile of all dialogue. Only change them if you know what you are doing."
|
||||
|
||||
msgid "settings.create_lines_for_responses_with_characters"
|
||||
msgstr "Create child dialogue line for responses with character names in them"
|
||||
|
||||
msgid "settings.open_in_external_editor"
|
||||
msgstr "Open dialogue files in external editor"
|
||||
|
||||
msgid "settings.external_editor_warning"
|
||||
msgstr "Note: Syntax highlighting and detailed error checking are not supported in external editors."
|
||||
|
||||
msgid "settings.include_characters_in_translations"
|
||||
msgstr "Include character names in translation exports"
|
||||
|
||||
msgid "settings.include_notes_in_translations"
|
||||
msgstr "Include notes (## comments) in translation exports"
|
||||
|
||||
msgid "settings.check_for_updates"
|
||||
msgstr "Check for updates"
|
||||
|
||||
msgid "n_of_n"
|
||||
msgstr "{index} of {total}"
|
||||
|
||||
@@ -294,9 +222,6 @@ msgstr "Titles cannot be empty."
|
||||
msgid "errors.duplicate_title"
|
||||
msgstr "There is already a title with that name."
|
||||
|
||||
msgid "errors.nested_title"
|
||||
msgstr "Titles cannot be indented."
|
||||
|
||||
msgid "errors.invalid_title_string"
|
||||
msgstr "Titles can only contain alphanumeric characters and numbers."
|
||||
|
||||
@@ -384,6 +309,21 @@ msgstr "Invalid index."
|
||||
msgid "errors.unexpected_assignment"
|
||||
msgstr "Unexpected assignment."
|
||||
|
||||
msgid "errors.expected_when_or_else"
|
||||
msgstr "Expecting a when or an else case."
|
||||
|
||||
msgid "errors.only_one_else_allowed"
|
||||
msgstr "Only one else case is allowed per match."
|
||||
|
||||
msgid "errors.when_must_belong_to_match"
|
||||
msgstr "When statements can only appear as children of match statements."
|
||||
|
||||
msgid "errors.concurrent_line_without_origin"
|
||||
msgstr "Concurrent lines need an origin line that doesn't start with \"| \"."
|
||||
|
||||
msgid "errors.goto_not_allowed_on_concurrect_lines"
|
||||
msgstr "Goto references are not allowed on concurrent dialogue lines."
|
||||
|
||||
msgid "errors.unknown"
|
||||
msgstr "Unknown syntax."
|
||||
|
||||
@@ -479,3 +419,9 @@ msgstr "Array[{type}] isn't supported in mutations. Use Array as a type instead.
|
||||
|
||||
msgid "runtime.dialogue_balloon_missing_start_method"
|
||||
msgstr "Your dialogue balloon is missing a \"start\" or \"Start\" method."
|
||||
|
||||
msgid "runtime.top_level_states_share_name"
|
||||
msgstr "Multiple top-level states ({states}) share method/property/signal name \"{key}\". Only the first occurance is accessible to dialogue."
|
||||
|
||||
msgid "translation_plugin.character_name"
|
||||
msgstr "Character name"
|
||||
@@ -41,9 +41,6 @@ msgstr "Insertar"
|
||||
msgid "translations"
|
||||
msgstr "Traducciones"
|
||||
|
||||
msgid "settings"
|
||||
msgstr "Ajustes"
|
||||
|
||||
msgid "show_support"
|
||||
msgstr "Contribuye con Dialogue Manager"
|
||||
|
||||
@@ -134,82 +131,6 @@ msgstr "Copiar la ruta del archivo"
|
||||
msgid "buffer.show_in_filesystem"
|
||||
msgstr "Mostrar en el sistema de archivos"
|
||||
|
||||
msgid "settings.invalid_test_scene"
|
||||
msgstr "\"{path}\" no extiende BaseDialogueTestScene."
|
||||
|
||||
msgid "settings.revert_to_default_test_scene"
|
||||
msgstr "Revertir a la escena de prueba por defecto"
|
||||
|
||||
msgid "settings.default_balloon_hint"
|
||||
msgstr ""
|
||||
"Globo personalizado para usar al llamar a \"DialogueManager.show_balloon()\""
|
||||
|
||||
msgid "settings.revert_to_default_balloon"
|
||||
msgstr "Volver al globo predeterminado"
|
||||
|
||||
msgid "settings.default_balloon_path"
|
||||
msgstr "<globo de ejemplo>"
|
||||
|
||||
msgid "settings.autoload"
|
||||
msgstr "Autocarga"
|
||||
|
||||
msgid "settings.path"
|
||||
msgstr "Ruta"
|
||||
|
||||
msgid "settings.new_template"
|
||||
msgstr "Los nuevos archivos de diálogo empezarán con una plantilla"
|
||||
|
||||
msgid "settings.missing_keys"
|
||||
msgstr "Tratar las claves de traducción faltantes como errores"
|
||||
|
||||
msgid "settings.missing_keys_hint"
|
||||
msgstr "Si estás utilizando claves de traducción estáticas, tener esta opción habilitada te ayudará a encontrar cualquier línea a la que aún no le hayas añadido una clave."
|
||||
|
||||
msgid "settings.characters_translations"
|
||||
msgstr "Exportar nombres de personajes en archivos de traducción"
|
||||
|
||||
msgid "settings.wrap_long_lines"
|
||||
msgstr "Romper líneas largas"
|
||||
|
||||
msgid "settings.include_failed_responses"
|
||||
msgstr "Incluir respuestas con condiciones fallidas"
|
||||
|
||||
msgid "settings.ignore_missing_state_values"
|
||||
msgstr "Omitir errores de valores de estado faltantes (no recomendado)"
|
||||
|
||||
msgid "settings.custom_test_scene"
|
||||
msgstr "Escena de prueba personalizada (debe extender BaseDialogueTestScene)"
|
||||
|
||||
msgid "settings.default_csv_locale"
|
||||
msgstr "Localización CSV por defecto"
|
||||
|
||||
msgid "settings.states_shortcuts"
|
||||
msgstr "Atajos de teclado"
|
||||
|
||||
msgid "settings.states_message"
|
||||
msgstr "Si un autoload está habilitado aquí, puedes referirte a sus propiedades y métodos sin tener que usar su nombre."
|
||||
|
||||
msgid "settings.states_hint"
|
||||
msgstr "ie. En lugar de \"SomeState.some_property\" podría simplemente usar \"some_property\""
|
||||
|
||||
msgid "settings.recompile_warning"
|
||||
msgstr "Cambiar estos ajustes obligará a recompilar todo el diálogo. Hazlo solo si sabes lo que estás haciendo."
|
||||
|
||||
msgid "settings.create_lines_for_responses_with_characters"
|
||||
msgstr "Crear línea de diálogo para respuestas con nombres de personajes dentro."
|
||||
|
||||
msgid "settings.open_in_external_editor"
|
||||
msgstr "Abrir archivos de diálogo en el editor externo"
|
||||
|
||||
msgid "settings.external_editor_warning"
|
||||
msgstr "Nota: El resaltado de sintaxis y la verificación detallada de errores no están soportados en editores externos."
|
||||
|
||||
msgid "settings.include_characters_in_translations"
|
||||
msgstr "Incluir nombres de personajes en las exportaciones de traducción"
|
||||
|
||||
msgid "settings.include_notes_in_translations"
|
||||
msgstr "Incluir notas (## comentarios) en las exportaciones de traducción"
|
||||
|
||||
msgid "n_of_n"
|
||||
msgstr "{index} de {total}"
|
||||
|
||||
|
||||
@@ -14,6 +14,9 @@ msgstr ""
|
||||
msgid "open.open"
|
||||
msgstr ""
|
||||
|
||||
msgid "open.quick_open"
|
||||
msgstr ""
|
||||
|
||||
msgid "open.no_recent_files"
|
||||
msgstr ""
|
||||
|
||||
@@ -23,12 +26,18 @@ msgstr ""
|
||||
msgid "save_all_files"
|
||||
msgstr ""
|
||||
|
||||
msgid "all"
|
||||
msgstr ""
|
||||
|
||||
msgid "find_in_files"
|
||||
msgstr ""
|
||||
|
||||
msgid "test_dialogue"
|
||||
msgstr ""
|
||||
|
||||
msgid "test_dialogue_from_line"
|
||||
msgstr ""
|
||||
|
||||
msgid "search_for_text"
|
||||
msgstr ""
|
||||
|
||||
@@ -38,9 +47,6 @@ msgstr ""
|
||||
msgid "translations"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings"
|
||||
msgstr ""
|
||||
|
||||
msgid "sponsor"
|
||||
msgstr ""
|
||||
|
||||
@@ -131,84 +137,6 @@ msgstr ""
|
||||
msgid "buffer.show_in_filesystem"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings.invalid_test_scene"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings.revert_to_default_test_scene"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings.default_balloon_hint"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings.revert_to_default_balloon"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings.default_balloon_path"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings.autoload"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings.path"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings.new_template"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings.missing_keys"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings.missing_keys_hint"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings.characters_translations"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings.wrap_long_lines"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings.include_failed_responses"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings.ignore_missing_state_values"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings.custom_test_scene"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings.default_csv_locale"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings.states_shortcuts"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings.states_message"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings.states_hint"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings.recompile_warning"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings.create_lines_for_responses_with_characters"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings.open_in_external_editor"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings.external_editor_warning"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings.include_characters_in_translations"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings.include_notes_in_translations"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings.check_for_updates"
|
||||
msgstr ""
|
||||
|
||||
msgid "n_of_n"
|
||||
msgstr ""
|
||||
|
||||
@@ -284,9 +212,6 @@ msgstr ""
|
||||
msgid "errors.duplicate_title"
|
||||
msgstr ""
|
||||
|
||||
msgid "errors.nested_title"
|
||||
msgstr ""
|
||||
|
||||
msgid "errors.invalid_title_string"
|
||||
msgstr ""
|
||||
|
||||
@@ -374,6 +299,21 @@ msgstr ""
|
||||
msgid "errors.unexpected_assignment"
|
||||
msgstr ""
|
||||
|
||||
msgid "errors.expected_when_or_else"
|
||||
msgstr ""
|
||||
|
||||
msgid "errors.only_one_else_allowed"
|
||||
msgstr ""
|
||||
|
||||
msgid "errors.when_must_belong_to_match"
|
||||
msgstr ""
|
||||
|
||||
msgid "errors.concurrent_line_without_origin"
|
||||
msgstr ""
|
||||
|
||||
msgid "errors.goto_not_allowed_on_concurrect_lines"
|
||||
msgstr ""
|
||||
|
||||
msgid "errors.unknown"
|
||||
msgstr ""
|
||||
|
||||
@@ -469,3 +409,9 @@ msgstr ""
|
||||
|
||||
msgid "runtime.dialogue_balloon_missing_start_method"
|
||||
msgstr ""
|
||||
|
||||
msgid "runtime.top_level_states_share_name"
|
||||
msgstr ""
|
||||
|
||||
msgid "translation_plugin.character_name"
|
||||
msgstr ""
|
||||
@@ -4,7 +4,7 @@ msgstr ""
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Veydzher\n"
|
||||
"Language-Team: \n"
|
||||
"Language: uk\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -20,8 +20,11 @@ msgstr "Відкрити файл"
|
||||
msgid "open.open"
|
||||
msgstr "Відкрити..."
|
||||
|
||||
msgid "open.quick_open"
|
||||
msgstr "Швидко відкрити..."
|
||||
|
||||
msgid "open.no_recent_files"
|
||||
msgstr "Немає недавніх файлів"
|
||||
msgstr "Жодних недавніх файлів"
|
||||
|
||||
msgid "open.clear_recent_files"
|
||||
msgstr "Очистити недавні файли"
|
||||
@@ -30,13 +33,16 @@ msgid "save_all_files"
|
||||
msgstr "Зберегти всі файли"
|
||||
|
||||
msgid "find_in_files"
|
||||
msgstr "Знайти у файліх..."
|
||||
msgstr "Знайти у файлах..."
|
||||
|
||||
msgid "test_dialogue"
|
||||
msgstr "Тестувати діалог"
|
||||
msgstr "Протестувати діалог з початку файлу"
|
||||
|
||||
msgid "test_dialogue_from_line"
|
||||
msgstr "Протестувати діалог з поточного рядка"
|
||||
|
||||
msgid "search_for_text"
|
||||
msgstr "Шукати текст"
|
||||
msgstr "Пошук тексту"
|
||||
|
||||
msgid "insert"
|
||||
msgstr "Вставити"
|
||||
@@ -44,9 +50,6 @@ msgstr "Вставити"
|
||||
msgid "translations"
|
||||
msgstr "Переклади"
|
||||
|
||||
msgid "settings"
|
||||
msgstr "Налаштування"
|
||||
|
||||
msgid "sponsor"
|
||||
msgstr "Спонсор"
|
||||
|
||||
@@ -84,7 +87,7 @@ msgid "insert.response"
|
||||
msgstr "Відповідь"
|
||||
|
||||
msgid "insert.random_lines"
|
||||
msgstr "Випадковий рядок"
|
||||
msgstr "Випадкові рядки"
|
||||
|
||||
msgid "insert.random_text"
|
||||
msgstr "Випадковий текст"
|
||||
@@ -93,7 +96,7 @@ msgid "insert.actions"
|
||||
msgstr "Дії"
|
||||
|
||||
msgid "insert.jump"
|
||||
msgstr "Перехід до заголовку"
|
||||
msgstr "Перейти до заголовку"
|
||||
|
||||
msgid "insert.end_dialogue"
|
||||
msgstr "Кінець діалогу"
|
||||
@@ -108,10 +111,10 @@ msgid "save_to_csv"
|
||||
msgstr "Зберегти рядки в CSV..."
|
||||
|
||||
msgid "import_from_csv"
|
||||
msgstr "Імпортувати зміни в рядках з CSV..."
|
||||
msgstr "Імпортувати зміни рядків з CSV..."
|
||||
|
||||
msgid "confirm_close"
|
||||
msgstr "Зберегти зміни до '{path}'?"
|
||||
msgstr "Зберегти зміни до «{path}»?"
|
||||
|
||||
msgid "confirm_close.save"
|
||||
msgstr "Зберегти зміни"
|
||||
@@ -138,85 +141,7 @@ msgid "buffer.copy_file_path"
|
||||
msgstr "Копіювати шлях файлу"
|
||||
|
||||
msgid "buffer.show_in_filesystem"
|
||||
msgstr "Показати у системі файлів"
|
||||
|
||||
msgid "settings.invalid_test_scene"
|
||||
msgstr "«{path}» не розширює BaseDialogueTestScene."
|
||||
|
||||
msgid "settings.revert_to_default_test_scene"
|
||||
msgstr "Повернутися до стандартної тестової сцени"
|
||||
|
||||
msgid "settings.default_balloon_hint"
|
||||
msgstr "Користувацьке діалогове вікно для використання під час виклику «DialogueManager.show_balloon()»"
|
||||
|
||||
msgid "settings.revert_to_default_balloon"
|
||||
msgstr "Повернутися до стандартного діалогового вікна"
|
||||
|
||||
msgid "settings.default_balloon_path"
|
||||
msgstr "<приклад діалогового вікна>"
|
||||
|
||||
msgid "settings.autoload"
|
||||
msgstr "Авто. завантаження"
|
||||
|
||||
msgid "settings.path"
|
||||
msgstr "Шлях"
|
||||
|
||||
msgid "settings.new_template"
|
||||
msgstr "Нові файли діалогів починатимуться з тексту шаблону"
|
||||
|
||||
msgid "settings.missing_keys"
|
||||
msgstr "Вважати відсутні ключі перекладу як помилками"
|
||||
|
||||
msgid "settings.missing_keys_hint"
|
||||
msgstr "Якщо ви використовуєте статичні ключі перекладу, увімкнення цього параметра допоможе вам знайти рядки, до яких ви ще не додали ключ."
|
||||
|
||||
msgid "settings.characters_translations"
|
||||
msgstr "Експорт імен персонажів у файлах перекладу"
|
||||
|
||||
msgid "settings.wrap_long_lines"
|
||||
msgstr "Переносити довгі рядки"
|
||||
|
||||
msgid "settings.include_failed_responses"
|
||||
msgstr "Включити відповіді з невдалими умовами"
|
||||
|
||||
msgid "settings.ignore_missing_state_values"
|
||||
msgstr "Пропускати помилки пропущених значень стану (не рекомендується)"
|
||||
|
||||
msgid "settings.custom_test_scene"
|
||||
msgstr "Користувацька тестова сцена (повинна розширювати BaseDialogueTestScene)"
|
||||
|
||||
msgid "settings.default_csv_locale"
|
||||
msgstr "Стандартна мова CSV"
|
||||
|
||||
msgid "settings.states_shortcuts"
|
||||
msgstr "Скорочення станів"
|
||||
|
||||
msgid "settings.states_message"
|
||||
msgstr "Якщо автозавантаження увімкнено, ви можете звертатися до його властивостей і методів без необхідності використовувати його назву."
|
||||
|
||||
msgid "settings.states_hint"
|
||||
msgstr "тобто, замість «ЯкийсьСтан.якась_властивість» ви можете просто використовувати «якусь_властивість»"
|
||||
|
||||
msgid "settings.recompile_warning"
|
||||
msgstr "Зміна цих параметрів призведе до перекомпіляції усіх діалогів. Змінюйте їх, тільки якщо ви знаєте, що робите."
|
||||
|
||||
msgid "settings.create_lines_for_responses_with_characters"
|
||||
msgstr "Створити діалогову лінію для відповідей дочірнього елемента з іменами персонажів"
|
||||
|
||||
msgid "settings.open_in_external_editor"
|
||||
msgstr "Відкрити файли діалогів у зовнішньому редакторі"
|
||||
|
||||
msgid "settings.external_editor_warning"
|
||||
msgstr "Примітка: Підсвічування синтаксису та детальна перевірка помилок не підтримуються у зовнішніх редакторах."
|
||||
|
||||
msgid "settings.include_characters_in_translations"
|
||||
msgstr "Включати імена персонажів до експорту перекладу"
|
||||
|
||||
msgid "settings.include_notes_in_translations"
|
||||
msgstr "Включати примітки (## коментарі) до експорту перекладу"
|
||||
|
||||
msgid "settings.check_for_updates"
|
||||
msgstr "Перевірити наявність оновлень"
|
||||
msgstr "Показати у файловій системі"
|
||||
|
||||
msgid "n_of_n"
|
||||
msgstr "{index} з {total}"
|
||||
@@ -234,7 +159,7 @@ msgid "search.replace_placeholder"
|
||||
msgstr "Текст для заміни"
|
||||
|
||||
msgid "search.replace_selected"
|
||||
msgstr "Замінити виділене"
|
||||
msgstr "Замінити вибране"
|
||||
|
||||
msgid "search.previous"
|
||||
msgstr "Назад"
|
||||
@@ -267,7 +192,7 @@ msgid "errors.key_not_found"
|
||||
msgstr "Ключ «{key}» не знайдено."
|
||||
|
||||
msgid "errors.line_and_message"
|
||||
msgstr "Помилка на {line}, {column}: {message}"
|
||||
msgstr "Помилка в {line}, {column}: {message}"
|
||||
|
||||
msgid "errors_in_script"
|
||||
msgstr "У вашому скрипті є помилки. Виправте їх і спробуйте ще раз."
|
||||
@@ -285,16 +210,13 @@ msgid "errors.duplicate_import"
|
||||
msgstr "Дублювання назви імпорту."
|
||||
|
||||
msgid "errors.unknown_using"
|
||||
msgstr "Невідоме автозавантаження в операторі використання."
|
||||
msgstr "Невідоме автозавантаження в операторі «using»."
|
||||
|
||||
msgid "errors.empty_title"
|
||||
msgstr "Заголовки не можуть бути порожніми."
|
||||
|
||||
msgid "errors.duplicate_title"
|
||||
msgstr "З такою назвою уже є заголовок."
|
||||
|
||||
msgid "errors.nested_title"
|
||||
msgstr "Заголовки не повинні мати відступів."
|
||||
msgstr "Заголовок з такою назвою уже є."
|
||||
|
||||
msgid "errors.invalid_title_string"
|
||||
msgstr "Заголовки можуть містити лише алфавітно-цифрові символи та цифри."
|
||||
@@ -306,7 +228,7 @@ msgid "errors.unknown_title"
|
||||
msgstr "Невідомий заголовок."
|
||||
|
||||
msgid "errors.jump_to_invalid_title"
|
||||
msgstr "Цей перехід вказує на недійсну назву."
|
||||
msgstr "Цей перехід вказує на недійсний заголовок."
|
||||
|
||||
msgid "errors.title_has_no_content"
|
||||
msgstr "Цей заголовок не має змісту. Можливо, варто змінити його на «=> END»."
|
||||
@@ -318,7 +240,7 @@ msgid "errors.unexpected_condition"
|
||||
msgstr "Несподівана умова."
|
||||
|
||||
msgid "errors.duplicate_id"
|
||||
msgstr "Цей ідентифікатор вже на іншому рядку."
|
||||
msgstr "Цей ідентифікатор уже є на іншому рядку."
|
||||
|
||||
msgid "errors.missing_id"
|
||||
msgstr "У цьому рядку відсутній ідентифікатор."
|
||||
@@ -383,6 +305,21 @@ msgstr "Недійсний індекс."
|
||||
msgid "errors.unexpected_assignment"
|
||||
msgstr "Несподіване призначення."
|
||||
|
||||
msgid "errors.expected_when_or_else"
|
||||
msgstr "Очікувався випадок «when» або «else»."
|
||||
|
||||
msgid "errors.only_one_else_allowed"
|
||||
msgstr "Для кожного «match» допускається лише один випадок «else»."
|
||||
|
||||
msgid "errors.when_must_belong_to_match"
|
||||
msgstr "Оператори «when» можуть з’являтися лише як дочірні операторів «match»."
|
||||
|
||||
msgid "errors.concurrent_line_without_origin"
|
||||
msgstr "Паралельні рядки потребують початкового рядка, який не починається з «|»."
|
||||
|
||||
msgid "errors.goto_not_allowed_on_concurrect_lines"
|
||||
msgstr "У паралельних діалогових рядках не допускаються Goto посилання."
|
||||
|
||||
msgid "errors.unknown"
|
||||
msgstr "Невідомий синтаксис."
|
||||
|
||||
@@ -399,7 +336,7 @@ msgid "update.download_update"
|
||||
msgstr "Завантажити оновлення"
|
||||
|
||||
msgid "update.needs_reload"
|
||||
msgstr "Щоб встановити оновлення, проєкт потрібно перезавантажити."
|
||||
msgstr "Щоб установити оновлення, проєкт потрібно перезавантажити."
|
||||
|
||||
msgid "update.reload_ok_button"
|
||||
msgstr "Перезавантажити проєкт"
|
||||
@@ -411,10 +348,10 @@ msgid "update.reload_project"
|
||||
msgstr "Перезавантажити проєкт"
|
||||
|
||||
msgid "update.release_notes"
|
||||
msgstr "Читати примітки оновлення"
|
||||
msgstr "Читати зміни оновлення"
|
||||
|
||||
msgid "update.success"
|
||||
msgstr "Менеджер діалогів тепер має версію {version}."
|
||||
msgstr "Dialogue Manager тепер з версією {version}."
|
||||
|
||||
msgid "update.failed"
|
||||
msgstr "Виникла проблема із завантаженням оновлення."
|
||||
@@ -441,22 +378,22 @@ msgid "runtime.array_index_out_of_bounds"
|
||||
msgstr "Індекс {index} виходить за межі масиву «{array}»."
|
||||
|
||||
msgid "runtime.left_hand_size_cannot_be_assigned_to"
|
||||
msgstr "Ліва частина виразу не може бути присвоєна."
|
||||
msgstr "Ліва частина виразу не може бути призначена."
|
||||
|
||||
msgid "runtime.key_not_found"
|
||||
msgstr "Ключ «{key}» у словнику «{dictionary}»"
|
||||
msgstr "Ключ «{key}» не знайдено у словнику «{dictionary}»"
|
||||
|
||||
msgid "runtime.property_not_found"
|
||||
msgstr "«{property}» не є властивістю для жодного стану гри ({states})."
|
||||
msgstr "«{property}» не знайдено. Стани з безпосередньо доступними властивостями/методами/сигналами включають {states}. На автозавантаження потрібно посилатися за їхніми назвами для використання їхніх властивостей."
|
||||
|
||||
msgid "runtime.property_not_found_missing_export"
|
||||
msgstr "«{property}» не є властивістю для жодного стану гри ({states}). Можливо, вам слід додати декоратор [Export]."
|
||||
msgstr "«{property}» не знайдено. Можливо, вам слід додати декоратор «[Export]». Стани з безпосередньо доступними властивостями/методами/сигналами включають {states}. На автозавантаження потрібно посилатися за їхніми назвами для використання їхніх властивостей."
|
||||
|
||||
msgid "runtime.method_not_found"
|
||||
msgstr "«{method}» не є методом на жодному зі станів гри ({states})"
|
||||
msgstr "Метод «{method}» не знайдено. Стани з безпосередньо доступними властивостями/методами/сигналами включають {states}. На автозавантаження потрібно посилатися за їхніми назвами для використання їхніх властивостей."
|
||||
|
||||
msgid "runtime.signal_not_found"
|
||||
msgstr "«{signal_name}» не є сигналом на жодному зі станів гри ({states})"
|
||||
msgstr "Сигнал «{signal_name}» не знайдено. Стани з безпосередньо доступними властивостями/методами/сигналами включають {states}. На автозавантаження потрібно посилатися за їхніми назвами для використання їхніх властивостей."
|
||||
|
||||
msgid "runtime.method_not_callable"
|
||||
msgstr "«{method}» не є методом, який можна викликати в «{object}»"
|
||||
@@ -465,16 +402,22 @@ msgid "runtime.unknown_operator"
|
||||
msgstr "Невідомий оператор."
|
||||
|
||||
msgid "runtime.unknown_autoload"
|
||||
msgstr "«{autoload}» не є дійсним автозавантаженням."
|
||||
msgstr "Схоже, «{autoload}» не є дійсним автозавантаженням."
|
||||
|
||||
msgid "runtime.something_went_wrong"
|
||||
msgstr "Щось пішло не так."
|
||||
|
||||
msgid "runtime.expected_n_got_n_args"
|
||||
msgstr "«{method}» було викликано з аргументами «{received}», але він має лише «{expected}»."
|
||||
msgstr "«{method}» було викликано з аргументами «{received}», але воно має лише «{expected}»."
|
||||
|
||||
msgid "runtime.unsupported_array_type"
|
||||
msgstr "Array[{type}] не підтримується в мутаціях. Натомість використовуйте Array як тип."
|
||||
msgstr "Array[{type}] не підтримується у модифікаціях. Натомість використовуйте Array як тип."
|
||||
|
||||
msgid "runtime.dialogue_balloon_missing_start_method"
|
||||
msgstr "У вашому діалоговому вікні відсутній метод «start» або «Start»."
|
||||
msgstr "У вашій кулі діалогу відсутній метод «start» або «Start»."
|
||||
|
||||
msgid "runtime.top_level_states_share_name"
|
||||
msgstr "Кілька станів верхнього рівня ({states}) мають спільну назву методу/властивості/сигналу «{key}». Для діалогу доступний лише перший випадок."
|
||||
|
||||
msgid "translation_plugin.character_name"
|
||||
msgstr "Ім’я персонажа"
|
||||
|
||||
@@ -44,9 +44,6 @@ msgstr "插入"
|
||||
msgid "translations"
|
||||
msgstr "翻译"
|
||||
|
||||
msgid "settings"
|
||||
msgstr "设置"
|
||||
|
||||
msgid "show_support"
|
||||
msgstr "支持 Dialogue Manager"
|
||||
|
||||
@@ -137,69 +134,6 @@ msgstr "复制文件路径"
|
||||
msgid "buffer.show_in_filesystem"
|
||||
msgstr "在 Godot 侧边栏中显示"
|
||||
|
||||
msgid "settings.revert_to_default_test_scene"
|
||||
msgstr "重置测试场景设定"
|
||||
|
||||
msgid "settings.default_balloon_hint"
|
||||
msgstr "设置调用 \"DialogueManager.show_balloon()\" 时使用的对话框"
|
||||
|
||||
msgid "settings.autoload"
|
||||
msgstr "Autoload"
|
||||
|
||||
msgid "settings.path"
|
||||
msgstr "路径"
|
||||
|
||||
msgid "settings.new_template"
|
||||
msgstr "新建文件时自动插入模板"
|
||||
|
||||
msgid "settings.missing_keys"
|
||||
msgstr "将翻译键缺失视为错误"
|
||||
|
||||
msgid "settings.missing_keys_hint"
|
||||
msgstr "如果你使用静态键,这将会帮助你寻找未添加至翻译文件的键。"
|
||||
|
||||
msgid "settings.characters_translations"
|
||||
msgstr "在翻译文件中导出角色名"
|
||||
|
||||
msgid "settings.wrap_long_lines"
|
||||
msgstr "文本编辑器自动换行"
|
||||
|
||||
msgid "settings.include_failed_responses"
|
||||
msgstr "在判断条件失败时仍显示回复选项"
|
||||
|
||||
msgid "settings.ignore_missing_state_values"
|
||||
msgstr "忽略全局变量缺失错误(不建议)"
|
||||
|
||||
msgid "settings.custom_test_scene"
|
||||
msgstr "自定义测试场景(必须继承自BaseDialogueTestScene)"
|
||||
|
||||
msgid "settings.default_csv_locale"
|
||||
msgstr "默认 CSV 区域格式"
|
||||
|
||||
msgid "settings.states_shortcuts"
|
||||
msgstr "全局变量映射"
|
||||
|
||||
msgid "settings.states_message"
|
||||
msgstr "当一个 Autoload 在这里被勾选,他的所有成员会被映射为全局变量。"
|
||||
|
||||
msgid "settings.states_hint"
|
||||
msgstr "比如,当你开启对于“Foo”的映射时,你可以将“Foo.bar”简写成“bar”。"
|
||||
|
||||
msgid "settings.recompile_warning"
|
||||
msgstr "更改这些选项会强制重新编译所有的对话框,当你清楚在做什么的时候更改。"
|
||||
|
||||
msgid "settings.create_lines_for_responses_with_characters"
|
||||
msgstr "回复项带角色名时(- char: response),会自动生成为选择后的下一句对话"
|
||||
|
||||
msgid "settings.include_characters_in_translations"
|
||||
msgstr "导出 CSV 时包括角色名"
|
||||
|
||||
msgid "settings.include_notes_in_translations"
|
||||
msgstr "导出 CSV 时包括注释(## comments)"
|
||||
|
||||
msgid "settings.check_for_updates"
|
||||
msgstr "检查升级"
|
||||
|
||||
msgid "n_of_n"
|
||||
msgstr "第{index}个,共{total}个"
|
||||
|
||||
@@ -272,9 +206,6 @@ msgstr "标题名不能为空。"
|
||||
msgid "errors.duplicate_title"
|
||||
msgstr "标题名不能重复。"
|
||||
|
||||
msgid "errors.nested_title"
|
||||
msgstr "标题不能嵌套。"
|
||||
|
||||
msgid "errors.invalid_title_string"
|
||||
msgstr "标题名无效。"
|
||||
|
||||
|
||||
@@ -44,9 +44,6 @@ msgstr "插入"
|
||||
msgid "translations"
|
||||
msgstr "翻譯"
|
||||
|
||||
msgid "settings"
|
||||
msgstr "設定"
|
||||
|
||||
msgid "show_support"
|
||||
msgstr "支援 Dialogue Manager"
|
||||
|
||||
@@ -137,69 +134,6 @@ msgstr "複製檔案位置"
|
||||
msgid "buffer.show_in_filesystem"
|
||||
msgstr "在 Godot 側邊欄中顯示"
|
||||
|
||||
msgid "settings.revert_to_default_test_scene"
|
||||
msgstr "重置測試場景設定"
|
||||
|
||||
msgid "settings.default_balloon_hint"
|
||||
msgstr "設置使用 \"DialogueManager.show_balloon()\" 时的对话框"
|
||||
|
||||
msgid "settings.autoload"
|
||||
msgstr "Autoload"
|
||||
|
||||
msgid "settings.path"
|
||||
msgstr "路徑"
|
||||
|
||||
msgid "settings.new_template"
|
||||
msgstr "新建檔案時自動插入模板"
|
||||
|
||||
msgid "settings.missing_keys"
|
||||
msgstr "將翻譯鍵缺失視爲錯誤"
|
||||
|
||||
msgid "settings.missing_keys_hint"
|
||||
msgstr "如果你使用靜態鍵,這將會幫助你尋找未添加至翻譯檔案的鍵。"
|
||||
|
||||
msgid "settings.wrap_long_lines"
|
||||
msgstr "自動折行"
|
||||
|
||||
msgid "settings.characters_translations"
|
||||
msgstr "在翻譯檔案中匯出角色名。"
|
||||
|
||||
msgid "settings.include_failed_responses"
|
||||
msgstr "在判斷條件失敗時仍顯示回復選項"
|
||||
|
||||
msgid "settings.ignore_missing_state_values"
|
||||
msgstr "忽略全局變量缺失錯誤(不建議)"
|
||||
|
||||
msgid "settings.custom_test_scene"
|
||||
msgstr "自訂測試場景(必須繼承自BaseDialogueTestScene)"
|
||||
|
||||
msgid "settings.default_csv_locale"
|
||||
msgstr "預設 CSV 區域格式"
|
||||
|
||||
msgid "settings.states_shortcuts"
|
||||
msgstr "全局變量映射"
|
||||
|
||||
msgid "settings.states_message"
|
||||
msgstr "當一個 Autoload 在這裏被勾選,他的所有成員會被映射爲全局變量。"
|
||||
|
||||
msgid "settings.states_hint"
|
||||
msgstr "比如,當你開啓對於“Foo”的映射時,你可以將“Foo.bar”簡寫成“bar”。"
|
||||
|
||||
msgid "settings.recompile_warning"
|
||||
msgstr "更改這些選項會強制重新編譯所有的對話框,當你清楚在做什麼的時候更改。"
|
||||
|
||||
msgid "settings.create_lines_for_responses_with_characters"
|
||||
msgstr "回覆項目帶角色名稱時(- char: response),會自動產生為選擇後的下一句對話"
|
||||
|
||||
msgid "settings.include_characters_in_translations"
|
||||
msgstr "匯出 CSV 時包含角色名"
|
||||
|
||||
msgid "settings.include_notes_in_translations"
|
||||
msgstr "匯出 CSV 時包括註解(## comments)"
|
||||
|
||||
msgid "settings.check_for_updates"
|
||||
msgstr "檢查升級"
|
||||
|
||||
msgid "n_of_n"
|
||||
msgstr "第{index}個,共{total}個"
|
||||
|
||||
@@ -272,9 +206,6 @@ msgstr "標題名不能爲空。"
|
||||
msgid "errors.duplicate_title"
|
||||
msgstr "標題名不能重複。"
|
||||
|
||||
msgid "errors.nested_title"
|
||||
msgstr "標題不能嵌套。"
|
||||
|
||||
msgid "errors.invalid_title_string"
|
||||
msgstr "標題名無效。"
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[plugin]
|
||||
|
||||
name="Dialogue Manager"
|
||||
description="A simple but powerful branching dialogue system"
|
||||
description="A powerful nonlinear dialogue system"
|
||||
author="Nathan Hoad"
|
||||
version="2.41.4"
|
||||
version="3.4.0"
|
||||
script="plugin.gd"
|
||||
|
||||
1
Zennysoft.Game.Ma/addons/dialogue_manager/plugin.cfg.uid
Normal file
1
Zennysoft.Game.Ma/addons/dialogue_manager/plugin.cfg.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://hrny2utekhei
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user