added a talking rock

This commit is contained in:
Egg Man 2024-09-07 14:12:04 -04:00
parent 1573768619
commit 55a291e2ae
79 changed files with 14358 additions and 17 deletions

View File

@ -1,4 +1,4 @@
[gd_scene load_steps=45 format=3 uid="uid://cny5b638kjd3w"]
[gd_scene load_steps=46 format=3 uid="uid://cny5b638kjd3w"]
[ext_resource type="Texture2D" uid="uid://3ysrchetuhic" path="res://Assets/Characters/Friendly/Tellik/Idle/Telick_standing_01.png" id="1_ify6l"]
[ext_resource type="Script" path="res://Assets/Characters/Friendly/Tellik/tellick.gd" id="1_q21sg"]
@ -178,6 +178,9 @@ animations = [{
radius = 21.0
height = 182.0
[sub_resource type="RectangleShape2D" id="RectangleShape2D_a2ym7"]
size = Vector2(41, 174.5)
[node name="Tellick" type="CharacterBody2D"]
z_index = 100
collision_layer = 2
@ -191,3 +194,13 @@ autoplay = "default"
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
shape = SubResource("CapsuleShape2D_aoset")
[node name="Marker2D" type="Marker2D" parent="."]
[node name="ActionableFinder" type="Area2D" parent="Marker2D"]
collision_layer = 0
collision_mask = 4
[node name="CollisionShape2D" type="CollisionShape2D" parent="Marker2D/ActionableFinder"]
position = Vector2(43, -2.75)
shape = SubResource("RectangleShape2D_a2ym7")

View File

@ -10,6 +10,14 @@ const FALL_GRAVITY_MULTIPLIER = 2
var jump_timer = 0.0
var is_jumping = false
@onready var actionableFinder: Area2D = $Marker2D/ActionableFinder
func _unhandled_input(event: InputEvent) -> void:
if Input.is_action_just_pressed("ui_accept"):
var actionables = actionableFinder.get_overlapping_areas()
if actionables.size() > 0:
actionables[0].action()
return
func _physics_process(delta):
# Apply gravity
if not is_on_floor():

View File

@ -0,0 +1,19 @@
[gd_scene load_steps=3 format=3 uid="uid://bop7ohwaq22g7"]
[ext_resource type="Texture2D" uid="uid://dtjckad6fcx2" path="res://Assets/World/Structures/Ruins_05.png" id="1_fuich"]
[sub_resource type="RectangleShape2D" id="RectangleShape2D_b6xvq"]
size = Vector2(14, 14)
[node name="Rock" type="Area2D"]
[node name="Sprite2D" type="Sprite2D" parent="."]
position = Vector2(1, 0)
scale = Vector2(0.291667, 0.291667)
texture = ExtResource("1_fuich")
region_enabled = true
region_rect = Rect2(2446.81, 2910.55, 48.8418, 48.8418)
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
position = Vector2(1, 0)
shape = SubResource("RectangleShape2D_b6xvq")

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 MiB

View File

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dtjckad6fcx2"
path="res://.godot/imported/Ruins_05.png-ed6606f614f644180620b26c0f496e61.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Assets/World/Structures/Ruins_05.png"
dest_files=["res://.godot/imported/Ruins_05.png-ed6606f614f644180620b26c0f496e61.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View File

@ -0,0 +1,7 @@
[gd_scene load_steps=2 format=3 uid="uid://bbhy8ac5hm1yx"]
[ext_resource type="Script" path="res://Scripts/actionable.gd" id="1_prwwk"]
[node name="Actionable" type="Area2D"]
collision_layer = 4
script = ExtResource("1_prwwk")

5
Dialouges/main.dialogue Normal file
View File

@ -0,0 +1,5 @@
~ start
Rock: OUCH!
=> END

View File

@ -0,0 +1,15 @@
[remap]
importer="dialogue_manager_compiler_12"
type="Resource"
uid="uid://bdqgwj58ijb4o"
path="res://.godot/imported/main.dialogue-9d46eb98f7a285d8cfb5c21ccf232434.tres"
[deps]
source_file="res://Dialouges/main.dialogue"
dest_files=["res://.godot/imported/main.dialogue-9d46eb98f7a285d8cfb5c21ccf232434.tres"]
[params]
defaults=true

9
Scripts/actionable.gd Normal file
View File

@ -0,0 +1,9 @@
extends Area2D
@export var dialouge_res: DialogueResource
@export var dialouge_start: String = "start"
func action() -> void:
DialogueManager.show_example_dialogue_balloon(dialouge_res, dialouge_start)

21
Scripts/dialouge.gd Normal file
View File

@ -0,0 +1,21 @@
extends CanvasLayer
@export(String, FILE, "*.json",) var d_file
var dia = []
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
start()
func start():
dia = load_dialouge()
$NinePatchRect/Title.text = dia[0]['name']
$NinePatchRect/Title.text = dia[0]['text']
func load_dialouge():
var file = file.new()
if file.file_exists(d_file):
file.open(d_file, file.READ)
return parse_json(file.get_as_text())

View File

@ -0,0 +1,423 @@
using Godot;
using Godot.Collections;
using System;
using System.Reflection;
using System.Threading.Tasks;
#nullable enable
namespace DialogueManagerRuntime
{
public enum TranslationSource
{
None,
Guess,
CSV,
PO
}
public partial class DialogueManager : Node
{
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 PassedTitleEventHandler? PassedTitle;
public static GotDialogueEventHandler? GotDialogue;
public static MutatedEventHandler? Mutated;
public static DialogueEndedEventHandler? DialogueEnded;
[Signal] public delegate void ResolvedEventHandler(Variant value);
private static GodotObject? instance;
public static GodotObject Instance
{
get
{
if (instance == null)
{
instance = Engine.GetSingleton("DialogueManager");
}
return instance;
}
}
public static Godot.Collections.Array GameStates
{
get => (Godot.Collections.Array)Instance.Get("game_states");
set => Instance.Set("game_states", value);
}
public static bool IncludeSingletons
{
get => (bool)Instance.Get("include_singletons");
set => Instance.Set("include_singletons", value);
}
public static bool IncludeClasses
{
get => (bool)Instance.Get("include_classes");
set => Instance.Set("include_classes", value);
}
public static TranslationSource TranslationSource
{
get => (TranslationSource)(int)Instance.Get("translation_source");
set => Instance.Set("translation_source", (int)value);
}
public static Func<Node> GetCurrentScene
{
set => Instance.Set("get_current_scene", Callable.From(value));
}
public void Prepare()
{
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;
var tree = Engine.GetMainLoop();
int x = 0;
// Try and find the singleton for a few seconds
while (!Engine.HasSingleton("DialogueManager") && x < 300)
{
await tree.ToSignal(tree, SceneTree.SignalName.ProcessFrame);
x++;
}
// If it times out something is wrong
if (x >= 300)
{
throw new Exception("The DialogueManager singleton is missing.");
}
instance = Engine.GetSingleton("DialogueManager");
return instance;
}
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");
if ((RefCounted)result[0] == null) return null;
return new DialogueLine((RefCounted)result[0]);
}
public static CanvasLayer ShowExampleDialogueBalloon(Resource dialogueResource, string key = "", Array<Variant>? extraGameStates = null)
{
return (CanvasLayer)Instance.Call("show_example_dialogue_balloon", dialogueResource, key, extraGameStates ?? new Array<Variant>());
}
public static Node ShowDialogueBalloonScene(string balloonScene, Resource dialogueResource, string key = "", Array<Variant>? extraGameStates = null)
{
return (Node)Instance.Call("show_dialogue_balloon_scene", balloonScene, dialogueResource, key, extraGameStates ?? new Array<Variant>());
}
public static Node ShowDialogueBalloonScene(PackedScene balloonScene, Resource dialogueResource, string key = "", Array<Variant>? extraGameStates = null)
{
return (Node)Instance.Call("show_dialogue_balloon_scene", balloonScene, dialogueResource, key, extraGameStates ?? new Array<Variant>());
}
public static Node ShowDialogueBalloonScene(Node balloonScene, Resource dialogueResource, string key = "", Array<Variant>? extraGameStates = null)
{
return (Node)Instance.Call("show_dialogue_balloon_scene", balloonScene, dialogueResource, key, extraGameStates ?? new Array<Variant>());
}
public static Node ShowDialogueBalloon(Resource dialogueResource, string key = "", Array<Variant>? extraGameStates = null)
{
return (Node)Instance.Call("show_dialogue_balloon", dialogueResource, key, extraGameStates ?? new Array<Variant>());
}
public static async void Mutate(Dictionary mutation, Array<Variant>? extraGameStates = null, bool isInlineMutation = false)
{
Instance.Call("_bridge_mutate", mutation, extraGameStates ?? new Array<Variant>(), isInlineMutation);
await Instance.ToSignal(Instance, "bridge_mutated");
}
public bool ThingHasMethod(GodotObject thing, string method)
{
MethodInfo? info = thing.GetType().GetMethod(method, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public);
return info != null;
}
public async void ResolveThingMethod(GodotObject thing, string method, Array<Variant> args)
{
MethodInfo? info = thing.GetType().GetMethod(method, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public);
if (info == null) return;
#nullable disable
// Convert the method args to something reflection can handle
ParameterInfo[] argTypes = info.GetParameters();
object[] _args = new object[argTypes.Length];
for (int i = 0; i < argTypes.Length; i++)
{
// check if args is assignable from derived type
if (i < args.Count && args[i].Obj != null)
{
if (argTypes[i].ParameterType.IsAssignableFrom(args[i].Obj.GetType()))
{
_args[i] = args[i].Obj;
}
// fallback to assigning primitive types
else
{
_args[i] = Convert.ChangeType(args[i].Obj, argTypes[i].ParameterType);
}
}
else if (argTypes[i].DefaultValue != null)
{
_args[i] = argTypes[i].DefaultValue;
}
}
// Add a single frame wait in case the method returns before signals can listen
await ToSignal(Engine.GetMainLoop(), SceneTree.SignalName.ProcessFrame);
// invoke method and handle the result based on return type
object result = info.Invoke(thing, _args);
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<>))
{
var resultProperty = taskType.GetProperty("Result");
var taskResultValue = resultProperty.GetValue(taskResult);
EmitSignal(SignalName.Resolved, (Variant)taskResultValue);
}
else
{
EmitSignal(SignalName.Resolved, null);
}
}
else
{
EmitSignal(SignalName.Resolved, (Variant)result);
}
}
#nullable enable
}
public partial class DialogueLine : RefCounted
{
private string id = "";
public string Id
{
get => id;
set => id = value;
}
private string type = "dialogue";
public string Type
{
get => type;
set => type = value;
}
private string next_id = "";
public string NextId
{
get => next_id;
set => next_id = value;
}
private string character = "";
public string Character
{
get => character;
set => character = value;
}
private string text = "";
public string Text
{
get => text;
set => text = value;
}
private string translation_key = "";
public string TranslationKey
{
get => translation_key;
set => translation_key = value;
}
private Array<DialogueResponse> responses = new Array<DialogueResponse>();
public Array<DialogueResponse> Responses
{
get => responses;
}
private string? time = null;
public string? Time
{
get => time;
}
private Dictionary pauses = new Dictionary();
public Dictionary Pauses
{
get => pauses;
}
private Dictionary speeds = new Dictionary();
public Dictionary Speeds
{
get => speeds;
}
private Array<Godot.Collections.Array> inline_mutations = new Array<Godot.Collections.Array>();
public Array<Godot.Collections.Array> InlineMutations
{
get => inline_mutations;
}
private Array<Variant> extra_game_states = new Array<Variant>();
private Array<string> tags = new Array<string>();
public Array<string> Tags
{
get => tags;
}
public DialogueLine(RefCounted data)
{
type = (string)data.Get("type");
next_id = (string)data.Get("next_id");
character = (string)data.Get("character");
text = (string)data.Get("text");
translation_key = (string)data.Get("translation_key");
pauses = (Dictionary)data.Get("pauses");
speeds = (Dictionary)data.Get("speeds");
inline_mutations = (Array<Godot.Collections.Array>)data.Get("inline_mutations");
time = (string)data.Get("time");
tags = (Array<string>)data.Get("tags");
foreach (var response in (Array<RefCounted>)data.Get("responses"))
{
responses.Add(new DialogueResponse(response));
}
}
public string GetTagValue(string tagName)
{
string wrapped = $"{tagName}=";
foreach (var tag in tags)
{
if (tag.StartsWith(wrapped))
{
return tag.Substring(wrapped.Length);
}
}
return "";
}
public override string ToString()
{
switch (type)
{
case "dialogue":
return $"<DialogueLine character=\"{character}\" text=\"{text}\">";
case "mutation":
return "<DialogueLine mutation>";
default:
return "";
}
}
}
public partial class DialogueResponse : RefCounted
{
private string next_id = "";
public string NextId
{
get => next_id;
set => next_id = value;
}
private bool is_allowed = true;
public bool IsAllowed
{
get => is_allowed;
set => is_allowed = value;
}
private string text = "";
public string Text
{
get => text;
set => text = value;
}
private string translation_key = "";
public string TranslationKey
{
get => translation_key;
set => translation_key = value;
}
private Array<string> tags = new Array<string>();
public Array<string> Tags
{
get => tags;
}
public DialogueResponse(RefCounted data)
{
next_id = (string)data.Get("next_id");
is_allowed = (bool)data.Get("is_allowed");
text = (string)data.Get("text");
translation_key = (string)data.Get("translation_key");
tags = (Array<string>)data.Get("tags");
}
public string GetTagValue(string tagName)
{
string wrapped = $"{tagName}=";
foreach (var tag in tags)
{
if (tag.StartsWith(wrapped))
{
return tag.Substring(wrapped.Length);
}
}
return "";
}
public override string ToString()
{
return $"<DialogueResponse text=\"{text}\"";
}
}
}

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022-present Nathan Hoad and Dialogue Manager contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

@ -0,0 +1,38 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://d3lr2uas6ax8v"
path="res://.godot/imported/icon.svg-17eb5d3e2a3cfbe59852220758c5b7bd.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/dialogue_manager/assets/icon.svg"
dest_files=["res://.godot/imported/icon.svg-17eb5d3e2a3cfbe59852220758c5b7bd.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=true
editor/convert_colors_with_editor_theme=true

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="16"
height="16"
viewBox="0 0 4.2333333 4.2333335"
version="1.1"
id="svg291"
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
sodipodi:docname="responses_menu.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview293"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="px"
showgrid="false"
width="1920px"
units="px"
borderlayer="true"
inkscape:showpageshadow="false"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="45.254834"
inkscape:cx="7.8334173"
inkscape:cy="6.5959804"
inkscape:window-width="2560"
inkscape:window-height="1377"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs288" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
id="rect181"
style="fill:#e0e0e0;fill-opacity:1;stroke:none;stroke-width:1.77487;stroke-linecap:round;stroke-linejoin:round;paint-order:stroke markers fill"
d="M 1.5875 0.26458334 L 1.5875 0.79375001 L 4.2333334 0.79375001 L 4.2333334 0.26458334 L 1.5875 0.26458334 z M 0 0.83147381 L 0 2.4189738 L 1.3229167 1.6252238 L 0 0.83147381 z M 1.5875 1.3229167 L 1.5875 1.8520834 L 4.2333334 1.8520834 L 4.2333334 1.3229167 L 1.5875 1.3229167 z M 1.5875 2.38125 L 1.5875 2.9104167 L 4.2333334 2.9104167 L 4.2333334 2.38125 L 1.5875 2.38125 z M 1.5875 3.4395834 L 1.5875 3.9687501 L 4.2333334 3.9687501 L 4.2333334 3.4395834 L 1.5875 3.4395834 z "
fill="#E0E0E0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,38 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://drjfciwitjm83"
path="res://.godot/imported/responses_menu.svg-87cf63ca685d53616205049572f4eb8f.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/dialogue_manager/assets/responses_menu.svg"
dest_files=["res://.godot/imported/responses_menu.svg-87cf63ca685d53616205049572f4eb8f.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=true
editor/convert_colors_with_editor_theme=true

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://d3baj6rygkb3f"
path="res://.godot/imported/update.svg-f1628866ed4eb2e13e3b81f75443687e.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/dialogue_manager/assets/update.svg"
dest_files=["res://.godot/imported/update.svg-f1628866ed4eb2e13e3b81f75443687e.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@ -0,0 +1,444 @@
@tool
extends CodeEdit
signal active_title_change(title: String)
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
# Theme overrides for syntax highlighting, etc
var theme_overrides: Dictionary:
set(value):
theme_overrides = value
syntax_highlighter = DialogueSyntaxHighlighter.new()
# General UI
add_theme_color_override("font_color", theme_overrides.text_color)
add_theme_color_override("background_color", theme_overrides.background_color)
add_theme_color_override("current_line_color", theme_overrides.current_line_color)
add_theme_font_override("font", get_theme_font("source", "EditorFonts"))
add_theme_font_size_override("font_size", theme_overrides.font_size * theme_overrides.scale)
font_size = round(theme_overrides.font_size)
get:
return theme_overrides
# Any parse errors
var errors: Array:
set(next_errors):
errors = next_errors
for i in range(0, get_line_count()):
var is_error: bool = false
for error in errors:
if error.line_number == i:
is_error = true
mark_line_as_error(i, is_error)
_on_code_edit_caret_changed()
get:
return errors
# The last selection (if there was one) so we can remember it for refocusing
var last_selected_text: String
var font_size: int:
set(value):
font_size = value
add_theme_font_size_override("font_size", font_size * theme_overrides.scale)
get:
return font_size
var WEIGHTED_RANDOM_PREFIX: RegEx = RegEx.create_from_string("^\\%[\\d.]+\\s")
func _ready() -> void:
# Add error gutter
add_gutter(0)
set_gutter_type(0, TextEdit.GUTTER_TYPE_ICON)
# Add comment delimiter
if not has_comment_delimiter("#"):
add_comment_delimiter("#", "", true)
syntax_highlighter = DialogueSyntaxHighlighter.new()
func _gui_input(event: InputEvent) -> void:
# Handle shortcuts that come from the editor
if event is InputEventKey and event.is_pressed():
var shortcut: String = Engine.get_meta("DialogueManagerPlugin").get_editor_shortcut(event)
match shortcut:
"toggle_comment":
toggle_comment()
get_viewport().set_input_as_handled()
"delete_line":
delete_current_line()
get_viewport().set_input_as_handled()
"move_up":
move_line(-1)
get_viewport().set_input_as_handled()
"move_down":
move_line(1)
get_viewport().set_input_as_handled()
"text_size_increase":
self.font_size += 1
get_viewport().set_input_as_handled()
"text_size_decrease":
self.font_size -= 1
get_viewport().set_input_as_handled()
"text_size_reset":
self.font_size = theme_overrides.font_size
get_viewport().set_input_as_handled()
elif event is InputEventMouse:
match event.as_text():
"Ctrl+Mouse Wheel Up", "Command+Mouse Wheel Up":
self.font_size += 1
get_viewport().set_input_as_handled()
"Ctrl+Mouse Wheel Down", "Command+Mouse Wheel Down":
self.font_size -= 1
get_viewport().set_input_as_handled()
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")
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")
for file in files:
# Don't import the file into itself
if file == main_view.current_file_path: continue
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")
for i in range(0, lines.size()):
if not lines[i].begins_with("import "):
insert_line_at(i, "import \"%s\" as %s\n" % [file, replace_regex.sub(path, "_", true)])
set_caret_line(i)
break
func _request_code_completion(force: bool) -> void:
var cursor: Vector2 = get_cursor()
var current_line: String = get_line(cursor.y)
if ("=> " in current_line or "=>< " in current_line) and (cursor.x > current_line.find("=>")):
var prompt: String = current_line.split("=>")[1]
if prompt.begins_with("< "):
prompt = prompt.substr(2)
else:
prompt = prompt.substr(1)
if "=> " in current_line:
if matches_prompt(prompt, "end"):
add_code_completion_option(CodeEdit.KIND_CLASS, "END", "END".substr(prompt.length()), theme_overrides.text_color, get_theme_icon("Stop", "EditorIcons"))
if matches_prompt(prompt, "end!"):
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:
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(), "")
if name_so_far != "" and name_so_far[0].to_upper() == name_so_far[0]:
# Only show names starting with that character
var names: PackedStringArray = get_character_names(name_so_far)
if names.size() > 0:
for name in names:
add_code_completion_option(CodeEdit.KIND_CLASS, name + ": ", name.substr(name_so_far.length()) + ": ", theme_overrides.text_color, get_theme_icon("Sprite2D", "EditorIcons"))
update_code_completion_options(true)
else:
cancel_code_completion()
func _filter_code_completion_candidates(candidates: Array) -> Array:
# Not sure why but if this method isn't overridden then all completions are wrapped in quotes.
return candidates
func _confirm_code_completion(replace: bool) -> void:
var completion = get_code_completion_option(get_code_completion_selected_index())
begin_complex_operation()
# Delete any part of the text that we've already typed
for i in range(0, completion.display_text.length() - completion.insert_text.length()):
backspace()
# Insert the whole match
insert_text_at_caret(completion.display_text)
end_complex_operation()
# Close the autocomplete menu on the next tick
call_deferred("cancel_code_completion")
### Helpers
# Get the current caret as a Vector2
func get_cursor() -> Vector2:
return Vector2(get_caret_column(), get_caret_line())
# 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)
# Check if a prompt is the start of a string without actually being that string
func matches_prompt(prompt: String, matcher: String) -> bool:
return prompt.length() < matcher.length() and matcher.to_lower().begins_with(prompt.to_lower())
## Get a list of titles from the current text
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())
return titles
## Work out what the next title above the current line is
func check_active_title() -> void:
var line_number = get_caret_line()
var lines = text.split("\n")
# Look at each line above this one to find the next title line
for i in range(line_number, -1, -1):
if lines[i].begins_with("~ "):
active_title_change.emit(lines[i].replace("~ ", ""))
return
active_title_change.emit("")
# Move the caret line to match a given title
func go_to_title(title: String) -> void:
var lines = text.split("\n")
for i in range(0, lines.size()):
if lines[i].strip_edges() == "~ " + title:
set_caret_line(i)
center_viewport_to_caret()
func get_character_names(beginning_with: String) -> PackedStringArray:
var names: PackedStringArray = []
var lines = text.split("\n")
for line in lines:
if ": " in line:
var name: String = WEIGHTED_RANDOM_PREFIX.sub(line.split(": ")[0].strip_edges(), "")
if not name in names and matches_prompt(beginning_with, name):
names.append(name)
return names
# Mark a line as an error or not
func mark_line_as_error(line_number: int, is_error: bool) -> void:
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"))
else:
set_line_background_color(line_number, theme_overrides.background_color)
set_line_gutter_icon(line_number, 0, null)
# Insert or wrap some bbcode at the caret/selection
func insert_bbcode(open_tag: String, close_tag: String = "") -> void:
if close_tag == "":
insert_text_at_caret(open_tag)
grab_focus()
else:
var selected_text = get_selected_text()
insert_text_at_caret("%s%s%s" % [open_tag, selected_text, close_tag])
grab_focus()
set_caret_column(get_caret_column() - close_tag.length())
# Insert text at current caret position
# Move Caret down 1 line if not => END
func insert_text_at_cursor(text: String) -> void:
if text != "=> END":
insert_text_at_caret(text+"\n")
set_caret_line(get_caret_line()+1)
else:
insert_text_at_caret(text)
grab_focus()
# Toggle the selected lines as comments
func toggle_comment() -> void:
begin_complex_operation()
var comment_delimiter: String = delimiter_comments[0]
var is_first_line: bool = true
var will_comment: bool = true
var selections: Array = []
var line_offsets: Dictionary = {}
for caret_index in range(0, get_caret_count()):
var from_line: int = get_caret_line(caret_index)
var from_column: int = get_caret_column(caret_index)
var to_line: int = get_caret_line(caret_index)
var to_column: int = get_caret_column(caret_index)
if has_selection(caret_index):
from_line = get_selection_from_line(caret_index)
to_line = get_selection_to_line(caret_index)
from_column = get_selection_from_column(caret_index)
to_column = get_selection_to_column(caret_index)
selections.append({
from_line = from_line,
from_column = from_column,
to_line = to_line,
to_column = to_column
})
for line_number in range(from_line, to_line + 1):
if line_offsets.has(line_number): continue
var line_text: String = get_line(line_number)
# The first line determines if we are commenting or uncommentingg
if is_first_line:
is_first_line = false
will_comment = not line_text.strip_edges().begins_with(comment_delimiter)
# Only comment/uncomment if the current line needs to
if will_comment:
set_line(line_number, comment_delimiter + line_text)
line_offsets[line_number] = 1
elif line_text.begins_with(comment_delimiter):
set_line(line_number, line_text.substr(comment_delimiter.length()))
line_offsets[line_number] = -1
else:
line_offsets[line_number] = 0
for caret_index in range(0, get_caret_count()):
var selection: Dictionary = selections[caret_index]
select(
selection.from_line,
selection.from_column + line_offsets[selection.from_line],
selection.to_line,
selection.to_column + line_offsets[selection.to_line],
caret_index
)
set_caret_column(selection.from_column + line_offsets[selection.from_line], false, caret_index)
end_complex_operation()
text_set.emit()
text_changed.emit()
# Remove the current line
func delete_current_line() -> void:
var cursor = get_cursor()
if get_line_count() == 1:
select_all()
elif cursor.y == 0:
select(0, 0, 1, 0)
else:
select(cursor.y - 1, get_line_width(cursor.y - 1), cursor.y, get_line_width(cursor.y))
delete_selection()
text_changed.emit()
# Move the selected lines up or down
func move_line(offset: int) -> void:
offset = clamp(offset, -1, 1)
var cursor = get_cursor()
var reselect: bool = false
var from: int = cursor.y
var to: int = cursor.y
if has_selection():
reselect = true
from = get_selection_from_line()
to = get_selection_to_line()
var lines := text.split("\n")
# Prevent the lines from being out of bounds
if from + offset < 0 or to + offset >= lines.size(): return
var target_from_index = from - 1 if offset == -1 else to + 1
var target_to_index = to if offset == -1 else from
var line_to_move = lines[target_from_index]
lines.remove_at(target_from_index)
lines.insert(target_to_index, line_to_move)
text = "\n".join(lines)
cursor.y += offset
from += offset
to += offset
if reselect:
select(from, 0, to, get_line_width(to))
set_cursor(cursor)
text_changed.emit()
### Signals
func _on_code_edit_symbol_validate(symbol: String) -> void:
if symbol.begins_with("res://") and symbol.ends_with(".dialogue"):
set_symbol_lookup_word_as_valid(true)
return
for title in get_titles():
if symbol == title:
set_symbol_lookup_word_as_valid(true)
return
set_symbol_lookup_word_as_valid(false)
func _on_code_edit_symbol_lookup(symbol: String, line: int, column: int) -> void:
if symbol.begins_with("res://") and symbol.ends_with(".dialogue"):
external_file_requested.emit(symbol, "")
else:
go_to_title(symbol)
func _on_code_edit_text_changed() -> void:
request_code_completion(true)
func _on_code_edit_text_set() -> void:
queue_redraw()
func _on_code_edit_caret_changed() -> void:
check_active_title()
last_selected_text = get_selected_text()
func _on_code_edit_gutter_clicked(line: int, gutter: int) -> void:
var line_errors = errors.filter(func(error): return error.line_number == line)
if line_errors.size() > 0:
error_clicked.emit(line)

View File

@ -0,0 +1,56 @@
[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"]
[sub_resource type="SyntaxHighlighter" id="SyntaxHighlighter_cobxx"]
script = ExtResource("1_58cfo")
[node name="CodeEdit" type="CodeEdit"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
text = "~ title_thing
if this = \"that\" or 'this'
Nathan: Something
- Then [if test.thing() == 2.0] => somewhere
- Other => END!
~ somewhere
set has_something = true
=> END"
highlight_all_occurrences = true
highlight_current_line = true
draw_tabs = true
syntax_highlighter = SubResource("SyntaxHighlighter_cobxx")
scroll_past_end_of_file = true
minimap_draw = true
symbol_lookup_on_click = true
line_folding = true
gutters_draw_line_numbers = true
gutters_draw_fold_gutter = true
delimiter_strings = Array[String](["\" \""])
delimiter_comments = Array[String](["#"])
code_completion_enabled = true
code_completion_prefixes = Array[String]([">", "<"])
indent_automatic = true
auto_brace_completion_enabled = true
auto_brace_completion_highlight_matching = true
auto_brace_completion_pairs = {
"\"": "\"",
"(": ")",
"[": "]",
"{": "}"
}
script = ExtResource("1_g324i")
[connection signal="caret_changed" from="." to="." method="_on_code_edit_caret_changed"]
[connection signal="gutter_clicked" from="." to="." method="_on_code_edit_gutter_clicked"]
[connection signal="symbol_lookup" from="." to="." method="_on_code_edit_symbol_lookup"]
[connection signal="symbol_validate" from="." to="." method="_on_code_edit_symbol_validate"]
[connection signal="text_changed" from="." to="." method="_on_code_edit_text_changed"]
[connection signal="text_set" from="." to="." method="_on_code_edit_text_set"]

View File

@ -0,0 +1,385 @@
@tool
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 cache: Dictionary = {}
func _notification(what: int) -> void:
if what == NOTIFICATION_PREDELETE:
dialogue_manager_parser.free()
func _clear_highlighting_cache() -> void:
cache = {}
## 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()
var text: String = text_edit.get_line(line)
# Prevent an error from popping up while developing
if not is_instance_valid(text_edit) or text_edit.theme_overrides.is_empty():
return colors
# Disable this, as well as the line at the bottom of this function to remove the cache.
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))
# 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)
# 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}
# 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}
# 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}
# 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}
# 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)
# Order the dictionary keys to prevent CodeEdit from having issues
var new_colors: Dictionary = {}
var ordered_keys: Array = colors.keys()
ordered_keys.sort()
for index in ordered_keys:
new_colors[index] = colors[index]
cache[text] = new_colors
return new_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 = {}
# #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)
else:
colors.merge(_get_literal_syntax_highlighting(start_index, text), true)
return colors
## 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 = {}
# 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

View File

@ -0,0 +1,168 @@
extends Node
const DialogueConstants = preload("../constants.gd")
const DialogueSettings = preload("../settings.gd")
const DialogueManagerParseResult = preload("./parse_result.gd")
signal file_content_changed(path: String, new_content: String)
# Keep track of errors and dependencies
# {
# <dialogue file path> = {
# path = <dialogue file path>,
# dependencies = [<dialogue file path>, <dialogue file path>],
# errors = [<error>, <error>]
# }
# }
var _cache: Dictionary = {}
var _update_dependency_timer: Timer = Timer.new()
var _update_dependency_paths: PackedStringArray = []
func _ready() -> void:
add_child(_update_dependency_timer)
_update_dependency_timer.timeout.connect(_on_update_dependency_timeout)
_build_cache()
func reimport_files(files: PackedStringArray = []) -> void:
if files.is_empty(): files = get_files()
var file_system: EditorFileSystem = Engine.get_meta("DialogueManagerPlugin") \
.get_editor_interface() \
.get_resource_filesystem()
# NOTE: Godot 4.2rc1 has an issue with reimporting more than one
# file at a time so we do them one by one
for file in files:
file_system.reimport_files([file])
await get_tree().create_timer(0.2)
## Add a dialogue file to the cache.
func add_file(path: String, parse_results: DialogueManagerParseResult = null) -> void:
_cache[path] = {
path = path,
dependencies = [],
errors = []
}
if parse_results != null:
_cache[path].dependencies = Array(parse_results.imported_paths).filter(func(d): return d != path)
_cache[path].parsed_at = Time.get_ticks_msec()
# If this is a fresh cache entry, check for dependencies
if parse_results == null and not _update_dependency_paths.has(path):
queue_updating_dependencies(path)
## Get the file paths in the cache
func get_files() -> PackedStringArray:
return _cache.keys()
## Check if a file is known to the cache
func has_file(path: String) -> bool:
return _cache.has(path)
## Remember any errors in a dialogue file
func add_errors_to_file(path: String, errors: Array[Dictionary]) -> void:
if _cache.has(path):
_cache[path].errors = errors
else:
_cache[path] = {
path = path,
resource_path = "",
dependencies = [],
errors = errors
}
## Get a list of files that have errors
func get_files_with_errors() -> Array[Dictionary]:
var files_with_errors: Array[Dictionary] = []
for dialogue_file in _cache.values():
if dialogue_file and dialogue_file.errors.size() > 0:
files_with_errors.append(dialogue_file)
return files_with_errors
## Queue a file to have its dependencies checked
func queue_updating_dependencies(of_path: String) -> void:
_update_dependency_timer.stop()
if not _update_dependency_paths.has(of_path):
_update_dependency_paths.append(of_path)
_update_dependency_timer.start(0.5)
## Update any references to a file path that has moved
func move_file_path(from_path: String, to_path: String) -> void:
if not _cache.has(from_path): return
if to_path != "":
_cache[to_path] = _cache[from_path].duplicate()
_cache.erase(from_path)
## Get every dialogue file that imports on a file of a given path
func get_files_with_dependency(imported_path: String) -> Array:
return _cache.values().filter(func(d): return d.dependencies.has(imported_path))
## Get any paths that are dependent on a given path
func get_dependent_paths_for_reimport(on_path: String) -> PackedStringArray:
return get_files_with_dependency(on_path) \
.filter(func(d): return Time.get_ticks_msec() - d.get("parsed_at", 0) > 3000) \
.map(func(d): return d.path)
# Build the initial cache for dialogue files
func _build_cache() -> void:
var current_files: PackedStringArray = _get_dialogue_files_in_filesystem()
for file in current_files:
add_file(file)
# Recursively find any dialogue files in a directory
func _get_dialogue_files_in_filesystem(path: String = "res://") -> PackedStringArray:
var files: PackedStringArray = []
if DirAccess.dir_exists_absolute(path):
var dir = DirAccess.open(path)
dir.list_dir_begin()
var file_name = dir.get_next()
while file_name != "":
var file_path: String = (path + "/" + file_name).simplify_path()
if dir.current_is_dir():
if not file_name in [".godot", ".tmp"]:
files.append_array(_get_dialogue_files_in_filesystem(file_path))
elif file_name.get_extension() == "dialogue":
files.append(file_path)
file_name = dir.get_next()
return files
### Signals
func _on_update_dependency_timeout() -> void:
_update_dependency_timer.stop()
var import_regex: RegEx = RegEx.create_from_string("import \"(?<path>.*?)\"")
var file: FileAccess
var found_imports: Array[RegExMatch]
for path in _update_dependency_paths:
# Open the file and check for any "import" lines
file = FileAccess.open(path, FileAccess.READ)
found_imports = import_regex.search_all(file.get_as_text())
var dependencies: PackedStringArray = []
for found in found_imports:
dependencies.append(found.strings[found.names.path])
_cache[path].dependencies = dependencies
_update_dependency_paths.clear()

View File

@ -0,0 +1,84 @@
@tool
extends Control
signal failed()
signal updated(updated_to_version: String)
const DialogueConstants = preload("../constants.gd")
const TEMP_FILE_NAME = "user://temp.zip"
@onready var logo: TextureRect = %Logo
@onready var label: Label = $VBox/Label
@onready var http_request: HTTPRequest = $HTTPRequest
@onready var download_button: Button = %DownloadButton
var next_version_release: Dictionary:
set(value):
next_version_release = value
label.text = DialogueConstants.translate(&"update.is_available_for_download") % value.tag_name.substr(1)
get:
return next_version_release
func _ready() -> void:
$VBox/Center/DownloadButton.text = DialogueConstants.translate(&"update.download_update")
$VBox/Center2/NotesButton.text = DialogueConstants.translate(&"update.release_notes")
### Signals
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"):
prints("You can't update the addon from within itself.")
failed.emit()
return
http_request.request(next_version_release.zipball_url)
download_button.disabled = true
download_button.text = DialogueConstants.translate(&"update.downloading")
func _on_http_request_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void:
if result != HTTPRequest.RESULT_SUCCESS:
failed.emit()
return
# Save the downloaded zip
var zip_file: FileAccess = FileAccess.open(TEMP_FILE_NAME, FileAccess.WRITE)
zip_file.store_buffer(body)
zip_file.close()
OS.move_to_trash(ProjectSettings.globalize_path("res://addons/dialogue_manager"))
var zip_reader: ZIPReader = ZIPReader.new()
zip_reader.open(TEMP_FILE_NAME)
var files: PackedStringArray = zip_reader.get_files()
var base_path = files[1]
# Remove archive folder
files.remove_at(0)
# Remove assets folder
files.remove_at(0)
for path in files:
var new_file_path: String = path.replace(base_path, "")
if path.ends_with("/"):
DirAccess.make_dir_recursive_absolute("res://addons/%s" % new_file_path)
else:
var file: FileAccess = FileAccess.open("res://addons/%s" % new_file_path, FileAccess.WRITE)
file.store_buffer(zip_reader.read_file(path))
zip_reader.close()
DirAccess.remove_absolute(TEMP_FILE_NAME)
updated.emit(next_version_release.tag_name.substr(1))
func _on_notes_button_pressed() -> void:
OS.shell_open(next_version_release.html_url)

View File

@ -0,0 +1,60 @@
[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="Texture2D" uid="uid://d3baj6rygkb3f" path="res://addons/dialogue_manager/assets/update.svg" id="2_4o2m6"]
[node name="DownloadUpdatePanel" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_4tm1k")
[node name="HTTPRequest" type="HTTPRequest" parent="."]
[node name="VBox" type="VBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -1.0
offset_top = 9.0
offset_right = -1.0
offset_bottom = 9.0
grow_horizontal = 2
grow_vertical = 2
theme_override_constants/separation = 10
[node name="Logo" type="TextureRect" parent="VBox"]
unique_name_in_owner = true
clip_contents = true
custom_minimum_size = Vector2(300, 80)
layout_mode = 2
texture = ExtResource("2_4o2m6")
stretch_mode = 5
[node name="Label" type="Label" parent="VBox"]
layout_mode = 2
text = "v1.2.3 is available for download."
horizontal_alignment = 1
[node name="Center" type="CenterContainer" parent="VBox"]
layout_mode = 2
[node name="DownloadButton" type="Button" parent="VBox/Center"]
unique_name_in_owner = true
layout_mode = 2
text = "Download update"
[node name="Center2" type="CenterContainer" parent="VBox"]
layout_mode = 2
[node name="NotesButton" type="LinkButton" parent="VBox/Center2"]
layout_mode = 2
text = "Read release notes"
[connection signal="request_completed" from="HTTPRequest" to="." method="_on_http_request_request_completed"]
[connection signal="pressed" from="VBox/Center/DownloadButton" to="." method="_on_download_button_pressed"]
[connection signal="pressed" from="VBox/Center2/NotesButton" to="." method="_on_notes_button_pressed"]

View File

@ -0,0 +1,48 @@
@tool
extends EditorProperty
const DialoguePropertyEditorControl = preload("./editor_property_control.tscn")
var editor_plugin: EditorPlugin
var control = DialoguePropertyEditorControl.instantiate()
var current_value: Resource
var is_updating: bool = false
func _init() -> void:
add_child(control)
control.resource = current_value
control.pressed.connect(_on_button_pressed)
control.resource_changed.connect(_on_resource_changed)
func _update_property() -> void:
var next_value = get_edited_object()[get_edited_property()]
# The resource might have been deleted elsewhere so check that it's not in a weird state
if is_instance_valid(next_value) and not next_value.resource_path.ends_with(".dialogue"):
emit_changed(get_edited_property(), null)
return
if next_value == current_value: return
is_updating = true
current_value = next_value
control.resource = current_value
is_updating = false
### Signals
func _on_button_pressed() -> void:
editor_plugin.edit(current_value)
func _on_resource_changed(next_resource: Resource) -> void:
emit_changed(get_edited_property(), next_resource)

View File

@ -0,0 +1,147 @@
@tool
extends HBoxContainer
signal pressed()
signal resource_changed(next_resource: Resource)
const ITEM_NEW = 100
const ITEM_QUICK_LOAD = 200
const ITEM_LOAD = 201
const ITEM_EDIT = 300
const ITEM_CLEAR = 301
const ITEM_FILESYSTEM = 400
@onready var button: Button = $ResourceButton
@onready var menu_button: Button = $MenuButton
@onready var menu: PopupMenu = $Menu
@onready var quick_open_dialog: ConfirmationDialog = $QuickOpenDialog
@onready var files_list = $QuickOpenDialog/FilesList
@onready var new_dialog: FileDialog = $NewDialog
@onready var open_dialog: FileDialog = $OpenDialog
var editor_plugin: EditorPlugin
var resource: Resource:
set(next_resource):
resource = next_resource
if button:
button.resource = resource
get:
return resource
var is_waiting_for_file: bool = false
var quick_selected_file: String = ""
func _ready() -> void:
menu_button.icon = get_theme_icon("GuiDropdown", "EditorIcons")
editor_plugin = Engine.get_meta("DialogueManagerPlugin")
func build_menu() -> void:
menu.clear()
menu.add_icon_item(editor_plugin._get_plugin_icon(), "New Dialogue", ITEM_NEW)
menu.add_separator()
menu.add_icon_item(get_theme_icon("Load", "EditorIcons"), "Quick Load", ITEM_QUICK_LOAD)
menu.add_icon_item(get_theme_icon("Load", "EditorIcons"), "Load", ITEM_LOAD)
if resource:
menu.add_icon_item(get_theme_icon("Edit", "EditorIcons"), "Edit", ITEM_EDIT)
menu.add_icon_item(get_theme_icon("Clear", "EditorIcons"), "Clear", ITEM_CLEAR)
menu.add_separator()
menu.add_item("Show in FileSystem", ITEM_FILESYSTEM)
menu.size = Vector2.ZERO
### Signals
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):
resource_changed.emit(load(path))
else:
var next_resource: Resource = await editor_plugin.import_plugin.compiled_resource
next_resource.resource_path = path
resource_changed.emit(next_resource)
func _on_open_dialog_file_selected(file: String) -> void:
resource_changed.emit(load(file))
func _on_file_dialog_canceled() -> void:
is_waiting_for_file = false
func _on_resource_button_pressed() -> void:
if is_instance_valid(resource):
editor_plugin.get_editor_interface().call_deferred("edit_resource", resource)
else:
build_menu()
menu.position = get_viewport().position + Vector2i(
button.global_position.x + button.size.x - menu.size.x,
2 + menu_button.global_position.y + button.size.y
)
menu.popup()
func _on_resource_button_resource_dropped(next_resource: Resource) -> void:
resource_changed.emit(next_resource)
func _on_menu_button_pressed() -> void:
build_menu()
menu.position = get_viewport().position + Vector2i(
menu_button.global_position.x + menu_button.size.x - menu.size.x,
2 + menu_button.global_position.y + menu_button.size.y
)
menu.popup()
func _on_menu_id_pressed(id: int) -> void:
match id:
ITEM_NEW:
is_waiting_for_file = true
new_dialog.popup_centered()
ITEM_QUICK_LOAD:
quick_selected_file = ""
files_list.files = Engine.get_meta("DialogueCache").get_files()
if resource:
files_list.select_file(resource.resource_path)
quick_open_dialog.popup_centered()
files_list.focus_filter()
ITEM_LOAD:
is_waiting_for_file = true
open_dialog.popup_centered()
ITEM_EDIT:
editor_plugin.get_editor_interface().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()
file_system.navigate_to_path(resource.resource_path)
func _on_files_list_file_double_clicked(file_path: String) -> void:
resource_changed.emit(load(file_path))
quick_open_dialog.hide()
func _on_files_list_file_selected(file_path: String) -> void:
quick_selected_file = file_path
func _on_quick_open_dialog_confirmed() -> void:
if quick_selected_file != "":
resource_changed.emit(load(quick_selected_file))

View File

@ -0,0 +1,58 @@
[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="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"]
[node name="PropertyEditorButton" type="HBoxContainer"]
offset_right = 40.0
offset_bottom = 40.0
size_flags_horizontal = 3
theme_override_constants/separation = 0
script = ExtResource("1_het12")
[node name="ResourceButton" parent="." instance=ExtResource("2_hh3d4")]
layout_mode = 2
text = "<empty>"
text_overrun_behavior = 3
clip_text = true
[node name="MenuButton" type="Button" parent="."]
layout_mode = 2
[node name="Menu" type="PopupMenu" parent="."]
[node name="QuickOpenDialog" type="ConfirmationDialog" parent="."]
title = "Find Dialogue Resource"
size = Vector2i(400, 600)
min_size = Vector2i(400, 600)
ok_button_text = "Open"
[node name="FilesList" parent="QuickOpenDialog" instance=ExtResource("3_l8fp6")]
[node name="NewDialog" type="FileDialog" parent="."]
size = Vector2i(900, 750)
min_size = Vector2i(900, 750)
dialog_hide_on_ok = true
filters = PackedStringArray("*.dialogue ; Dialogue")
[node name="OpenDialog" type="FileDialog" parent="."]
title = "Open a File"
size = Vector2i(900, 750)
min_size = Vector2i(900, 750)
ok_button_text = "Open"
dialog_hide_on_ok = true
file_mode = 0
filters = PackedStringArray("*.dialogue ; Dialogue")
[connection signal="pressed" from="ResourceButton" to="." method="_on_resource_button_pressed"]
[connection signal="resource_dropped" from="ResourceButton" to="." method="_on_resource_button_resource_dropped"]
[connection signal="pressed" from="MenuButton" to="." method="_on_menu_button_pressed"]
[connection signal="id_pressed" from="Menu" to="." method="_on_menu_id_pressed"]
[connection signal="confirmed" from="QuickOpenDialog" to="." method="_on_quick_open_dialog_confirmed"]
[connection signal="file_double_clicked" from="QuickOpenDialog/FilesList" to="." method="_on_files_list_file_double_clicked"]
[connection signal="file_selected" from="QuickOpenDialog/FilesList" to="." method="_on_files_list_file_selected"]
[connection signal="canceled" from="NewDialog" to="." method="_on_file_dialog_canceled"]
[connection signal="file_selected" from="NewDialog" to="." method="_on_new_dialog_file_selected"]
[connection signal="canceled" from="OpenDialog" to="." method="_on_file_dialog_canceled"]
[connection signal="file_selected" from="OpenDialog" to="." method="_on_open_dialog_file_selected"]

View File

@ -0,0 +1,48 @@
@tool
extends Button
signal resource_dropped(next_resource: Resource)
var resource: Resource:
set(next_resource):
resource = next_resource
if resource:
icon = Engine.get_meta("DialogueManagerPlugin")._get_plugin_icon()
text = resource.resource_path.get_file().replace(".dialogue", "")
else:
icon = null
text = "<empty>"
get:
return resource
func _notification(what: int) -> void:
match what:
NOTIFICATION_DRAG_BEGIN:
var data = get_viewport().gui_get_drag_data()
if typeof(data) == TYPE_DICTIONARY and data.type == "files" and data.files.size() > 0 and data.files[0].ends_with(".dialogue"):
add_theme_stylebox_override("normal", get_theme_stylebox("focus", "LineEdit"))
add_theme_stylebox_override("hover", get_theme_stylebox("focus", "LineEdit"))
NOTIFICATION_DRAG_END:
self.resource = resource
remove_theme_stylebox_override("normal")
remove_theme_stylebox_override("hover")
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")
return files.size() > 0
func _drop_data(at_position: Vector2, data) -> void:
var files: PackedStringArray = Array(data.files).filter(func(f): return f.get_extension() == "dialogue")
if files.size() == 0: return
resource_dropped.emit(load(files[0]))

View File

@ -0,0 +1,9 @@
[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"]
[node name="ResourceButton" type="Button"]
offset_right = 8.0
offset_bottom = 8.0
size_flags_horizontal = 3
script = ExtResource("1_7u2i7")

View File

@ -0,0 +1,85 @@
@tool
extends HBoxContainer
signal error_pressed(line_number)
const DialogueConstants = preload("../constants.gd")
@onready var error_button: Button = $ErrorButton
@onready var next_button: Button = $NextButton
@onready var count_label: Label = $CountLabel
@onready var previous_button: Button = $PreviousButton
## The index of the current error being shown
var error_index: int = 0:
set(next_error_index):
error_index = wrap(next_error_index, 0, errors.size())
show_error()
get:
return error_index
## The list of all errors
var errors: Array = []:
set(next_errors):
errors = next_errors
self.error_index = 0
get:
return errors
func _ready() -> void:
apply_theme()
hide()
## Set up colors and icons
func apply_theme() -> void:
error_button.add_theme_color_override("font_color", get_theme_color("error_color", "Editor"))
error_button.add_theme_color_override("font_hover_color", get_theme_color("error_color", "Editor"))
error_button.icon = get_theme_icon("StatusError", "EditorIcons")
previous_button.icon = get_theme_icon("ArrowLeft", "EditorIcons")
next_button.icon = get_theme_icon("ArrowRight", "EditorIcons")
## Move the error index to match a given line
func show_error_for_line_number(line_number: int) -> void:
for i in range(0, errors.size()):
if errors[i].line_number == line_number:
self.error_index = i
## Show the current error
func show_error() -> void:
if errors.size() == 0:
hide()
else:
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) })
if error.has("external_error"):
error_button.text += " " + DialogueConstants.get_error_message(error.external_error)
### Signals
func _on_errors_panel_theme_changed() -> void:
apply_theme()
func _on_error_button_pressed() -> void:
emit_signal("error_pressed", errors[error_index].line_number, errors[error_index].column_number)
func _on_previous_button_pressed() -> void:
self.error_index -= 1
_on_error_button_pressed()
func _on_next_button_pressed() -> void:
self.error_index += 1
_on_error_button_pressed()

View File

@ -0,0 +1,56 @@
[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"]
[sub_resource type="Image" id="Image_wy5pj"]
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",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="ImageTexture_s6fxl"]
image = SubResource("Image_wy5pj")
[node name="ErrorsPanel" type="HBoxContainer"]
visible = false
offset_right = 1024.0
offset_bottom = 600.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_nfm3c")
metadata/_edit_layout_mode = 1
[node name="ErrorButton" type="Button" parent="."]
layout_mode = 2
size_flags_horizontal = 3
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_hover_color = Color(0, 0, 0, 1)
theme_override_constants/h_separation = 3
icon = SubResource("ImageTexture_s6fxl")
flat = true
alignment = 0
text_overrun_behavior = 4
[node name="Spacer" type="Control" parent="."]
custom_minimum_size = Vector2(40, 0)
layout_mode = 2
[node name="PreviousButton" type="Button" parent="."]
layout_mode = 2
icon = SubResource("ImageTexture_s6fxl")
flat = true
[node name="CountLabel" type="Label" parent="."]
layout_mode = 2
[node name="NextButton" type="Button" parent="."]
layout_mode = 2
icon = SubResource("ImageTexture_s6fxl")
flat = true
[connection signal="pressed" from="ErrorButton" to="." method="_on_error_button_pressed"]
[connection signal="pressed" from="PreviousButton" to="." method="_on_previous_button_pressed"]
[connection signal="pressed" from="NextButton" to="." method="_on_next_button_pressed"]

View File

@ -0,0 +1,146 @@
@tool
extends VBoxContainer
signal file_selected(file_path: String)
signal file_popup_menu_requested(at_position: Vector2)
signal file_double_clicked(file_path: String)
signal file_middle_clicked(file_path: String)
const DialogueConstants = preload("../constants.gd")
const MODIFIED_SUFFIX = "(*)"
@export var icon: Texture2D
@onready var filter_edit: LineEdit = $FilterEdit
@onready var list: ItemList = $List
var file_map: Dictionary = {}
var current_file_path: String = ""
var files: PackedStringArray = []:
set(next_files):
files = next_files
files.sort()
update_file_map()
apply_filter()
get:
return files
var unsaved_files: Array[String] = []
var filter: String:
set(next_filter):
filter = next_filter
apply_filter()
get:
return filter
func _ready() -> void:
apply_theme()
filter_edit.placeholder_text = DialogueConstants.translate(&"files_list.filter")
func focus_filter() -> void:
filter_edit.grab_focus()
func select_file(file: String) -> void:
list.deselect_all()
for i in range(0, list.get_item_count()):
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)
func mark_file_as_unsaved(file: String, is_unsaved: bool) -> void:
if not file in unsaved_files and is_unsaved:
unsaved_files.append(file)
elif file in unsaved_files and not is_unsaved:
unsaved_files.erase(file)
apply_filter()
func update_file_map() -> void:
file_map = {}
for file in files:
var nice_file: String = get_nice_file(file)
# See if a value with just the file name is already in the map
for key in file_map.keys():
if file_map[key] == nice_file:
var bit_count = nice_file.count("/") + 2
var existing_nice_file = get_nice_file(key, bit_count)
nice_file = get_nice_file(file, bit_count)
while nice_file == existing_nice_file:
bit_count += 1
existing_nice_file = get_nice_file(key, bit_count)
nice_file = get_nice_file(file, bit_count)
file_map[key] = existing_nice_file
file_map[file] = nice_file
func get_nice_file(file_path: String, path_bit_count: int = 1) -> String:
var bits = file_path.replace("res://", "").replace(".dialogue", "").split("/")
bits = bits.slice(-path_bit_count)
return "/".join(bits)
func apply_filter() -> void:
list.clear()
for file in file_map.keys():
if filter == "" or filter.to_lower() in file.to_lower():
var nice_file = file_map[file]
if file in unsaved_files:
nice_file += MODIFIED_SUFFIX
var new_id := list.add_item(nice_file)
list.set_item_icon(new_id, icon)
select_file(current_file_path)
func apply_theme() -> void:
if is_instance_valid(filter_edit):
filter_edit.right_icon = get_theme_icon("Search", "EditorIcons")
### Signals
func _on_theme_changed() -> void:
apply_theme()
func _on_filter_edit_text_changed(new_text: String) -> void:
self.filter = new_text
func _on_list_item_clicked(index: int, at_position: Vector2, mouse_button_index: int) -> void:
var item_text = list.get_item_text(index).replace(MODIFIED_SUFFIX, "")
var file = file_map.find_key(item_text)
if mouse_button_index == MOUSE_BUTTON_LEFT or mouse_button_index == MOUSE_BUTTON_RIGHT:
select_file(file)
file_selected.emit(file)
if mouse_button_index == MOUSE_BUTTON_RIGHT:
file_popup_menu_requested.emit(at_position)
if mouse_button_index == MOUSE_BUTTON_MIDDLE:
file_middle_clicked.emit(file)
func _on_list_item_activated(index: int) -> void:
var item_text = list.get_item_text(index).replace(MODIFIED_SUFFIX, "")
var file = file_map.find_key(item_text)
select_file(file)
file_double_clicked.emit(file)

View File

@ -0,0 +1,28 @@
[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="Texture2D" uid="uid://d3lr2uas6ax8v" path="res://addons/dialogue_manager/assets/icon.svg" id="2_3ijx1"]
[node name="FilesList" type="VBoxContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_cytii")
icon = ExtResource("2_3ijx1")
[node name="FilterEdit" type="LineEdit" parent="."]
layout_mode = 2
placeholder_text = "Filter files"
clear_button_enabled = true
[node name="List" type="ItemList" parent="."]
layout_mode = 2
size_flags_vertical = 3
allow_rmb_select = true
[connection signal="theme_changed" from="." to="." method="_on_theme_changed"]
[connection signal="text_changed" from="FilterEdit" to="." method="_on_filter_edit_text_changed"]
[connection signal="item_activated" from="List" to="." method="_on_list_item_activated"]
[connection signal="item_clicked" from="List" to="." method="_on_list_item_clicked"]

View File

@ -0,0 +1,229 @@
@tool
extends Control
signal result_selected(path: String, cursor: Vector2, length: int)
const DialogueConstants = preload("../constants.gd")
@export var main_view: Control
@export var code_edit: CodeEdit
@onready var input: LineEdit = %Input
@onready var search_button: Button = %SearchButton
@onready var match_case_button: CheckBox = %MatchCaseButton
@onready var replace_toggle: CheckButton = %ReplaceToggle
@onready var replace_container: VBoxContainer = %ReplaceContainer
@onready var replace_input: LineEdit = %ReplaceInput
@onready var replace_selected_button: Button = %ReplaceSelectedButton
@onready var replace_all_button: Button = %ReplaceAllButton
@onready var results_container: VBoxContainer = %ResultsContainer
@onready var result_template: HBoxContainer = %ResultTemplate
var current_results: Dictionary = {}:
set(value):
current_results = value
update_results_view()
if current_results.size() == 0:
replace_selected_button.disabled = true
replace_all_button.disabled = true
else:
replace_selected_button.disabled = false
replace_all_button.disabled = false
get:
return current_results
var selections: PackedStringArray = []
func prepare() -> void:
input.grab_focus()
var template_label = result_template.get_node("Label")
template_label.get_theme_stylebox(&"focus").bg_color = code_edit.theme_overrides.current_line_color
template_label.add_theme_font_override(&"normal_font", code_edit.get_theme_font(&"font"))
replace_toggle.set_pressed_no_signal(false)
replace_container.hide()
$VBoxContainer/HBoxContainer/FindContainer/Label.text = DialogueConstants.translate(&"search.find")
input.placeholder_text = DialogueConstants.translate(&"search.placeholder")
input.text = ""
search_button.text = DialogueConstants.translate(&"search.find_all")
match_case_button.text = DialogueConstants.translate(&"search.match_case")
replace_toggle.text = DialogueConstants.translate(&"search.toggle_replace")
$VBoxContainer/HBoxContainer/ReplaceContainer/ReplaceLabel.text = DialogueConstants.translate(&"search.replace_with")
replace_input.placeholder_text = DialogueConstants.translate(&"search.replace_placeholder")
replace_input.text = ""
replace_all_button.text = DialogueConstants.translate(&"search.replace_all")
replace_selected_button.text = DialogueConstants.translate(&"search.replace_selected")
selections.clear()
self.current_results = {}
#region helpers
func update_results_view() -> void:
for child in results_container.get_children():
child.queue_free()
for path in current_results.keys():
var path_label: Label = Label.new()
path_label.text = path
# Show open files
if main_view.open_buffers.has(path):
path_label.text += "(*)"
results_container.add_child(path_label)
for path_result in current_results.get(path):
var result_item: HBoxContainer = result_template.duplicate()
var checkbox: CheckBox = result_item.get_node("CheckBox") as CheckBox
var key: String = get_selection_key(path, path_result)
checkbox.toggled.connect(func(is_pressed):
if is_pressed:
if not selections.has(key):
selections.append(key)
else:
if selections.has(key):
selections.remove_at(selections.find(key))
)
checkbox.set_pressed_no_signal(selections.has(key))
checkbox.visible = replace_toggle.button_pressed
var result_label: RichTextLabel = result_item.get_node("Label") as RichTextLabel
var colors: Dictionary = code_edit.theme_overrides
var highlight: String = ""
if replace_toggle.button_pressed:
var matched_word: String = "[bgcolor=" + colors.critical_color.to_html() + "][color=" + colors.text_color.to_html() + "]" + path_result.matched_text + "[/color][/bgcolor]"
highlight = "[s]" + matched_word + "[/s][bgcolor=" + colors.notice_color.to_html() + "][color=" + colors.text_color.to_html() + "]" + replace_input.text + "[/color][/bgcolor]"
else:
highlight = "[bgcolor=" + colors.symbols_color.to_html() + "][color=" + colors.text_color.to_html() + "]" + path_result.matched_text + "[/color][/bgcolor]"
var text: String = path_result.text.substr(0, path_result.index) + highlight + path_result.text.substr(path_result.index + path_result.query.length())
result_label.text = "%s: %s" % [str(path_result.line).lpad(4), text]
result_label.gui_input.connect(func(event):
if event is InputEventMouseButton and (event as InputEventMouseButton).button_index == MOUSE_BUTTON_LEFT and (event as InputEventMouseButton).double_click:
result_selected.emit(path, Vector2(path_result.index, path_result.line), path_result.query.length())
)
results_container.add_child(result_item)
func find_in_files() -> Dictionary:
var results: Dictionary = {}
var q: String = input.text
var cache = Engine.get_meta("DialogueCache")
var file: FileAccess
for path in cache.get_files():
var path_results: Array = []
var lines: PackedStringArray = []
if main_view.open_buffers.has(path):
lines = main_view.open_buffers.get(path).text.split("\n")
else:
file = FileAccess.open(path, FileAccess.READ)
lines = file.get_as_text().split("\n")
for i in range(0, lines.size()):
var index: int = find_in_line(lines[i], q)
while index > -1:
path_results.append({
line = i,
index = index,
text = lines[i],
matched_text = lines[i].substr(index, q.length()),
query = q
})
index = find_in_line(lines[i], q, index + q.length())
if file != null and file.is_open():
file.close()
if path_results.size() > 0:
results[path] = path_results
return results
func get_selection_key(path: String, path_result: Dictionary) -> String:
return "%s-%d-%d" % [path, path_result.line, path_result.index]
func find_in_line(line: String, query: String, from_index: int = 0) -> int:
if match_case_button.button_pressed:
return line.find(query, from_index)
else:
return line.findn(query, from_index)
func replace_results(only_selected: bool) -> void:
var file: FileAccess
var lines: PackedStringArray = []
for path in current_results:
if main_view.open_buffers.has(path):
lines = main_view.open_buffers.get(path).text.split("\n")
else:
file = FileAccess.open(path, FileAccess.READ_WRITE)
lines = file.get_as_text().split("\n")
# Read the results in reverse because we're going to be modifying them as we go
var path_results: Array = current_results.get(path).duplicate()
path_results.reverse()
for path_result in path_results:
var key: String = get_selection_key(path, path_result)
if not only_selected or (only_selected and selections.has(key)):
lines[path_result.line] = lines[path_result.line].substr(0, path_result.index) + replace_input.text + lines[path_result.line].substr(path_result.index + path_result.matched_text.length())
var replaced_text: String = "\n".join(lines)
if file != null and file.is_open():
file.seek(0)
file.store_string(replaced_text)
file.close()
else:
main_view.open_buffers.get(path).text = replaced_text
if main_view.current_file_path == path:
code_edit.text = replaced_text
current_results = find_in_files()
#endregion
#region signals
func _on_search_button_pressed() -> void:
selections.clear()
self.current_results = find_in_files()
func _on_input_text_submitted(new_text: String) -> void:
_on_search_button_pressed()
func _on_replace_toggle_toggled(toggled_on: bool) -> void:
replace_container.visible = toggled_on
if toggled_on:
replace_input.grab_focus()
update_results_view()
func _on_replace_input_text_changed(new_text: String) -> void:
update_results_view()
func _on_replace_selected_button_pressed() -> void:
replace_results(true)
func _on_replace_all_button_pressed() -> void:
replace_results(false)
func _on_match_case_button_toggled(toggled_on: bool) -> void:
_on_search_button_pressed()
#endregion

View File

@ -0,0 +1,139 @@
[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"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_owohg"]
bg_color = Color(0.266667, 0.278431, 0.352941, 0.243137)
corner_detail = 1
[node name="FindInFiles" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 3
script = ExtResource("1_3xicy")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="FindContainer" type="VBoxContainer" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="Label" type="Label" parent="VBoxContainer/HBoxContainer/FindContainer"]
layout_mode = 2
text = "Find:"
[node name="Input" type="LineEdit" parent="VBoxContainer/HBoxContainer/FindContainer"]
unique_name_in_owner = true
layout_mode = 2
clear_button_enabled = true
[node name="FindToolbar" type="HBoxContainer" parent="VBoxContainer/HBoxContainer/FindContainer"]
layout_mode = 2
[node name="SearchButton" type="Button" parent="VBoxContainer/HBoxContainer/FindContainer/FindToolbar"]
unique_name_in_owner = true
layout_mode = 2
text = "Find all..."
[node name="MatchCaseButton" type="CheckBox" parent="VBoxContainer/HBoxContainer/FindContainer/FindToolbar"]
unique_name_in_owner = true
layout_mode = 2
text = "Match case"
[node name="Control" type="Control" parent="VBoxContainer/HBoxContainer/FindContainer/FindToolbar"]
layout_mode = 2
size_flags_horizontal = 3
[node name="ReplaceToggle" type="CheckButton" parent="VBoxContainer/HBoxContainer/FindContainer/FindToolbar"]
unique_name_in_owner = true
layout_mode = 2
text = "Replace"
[node name="ReplaceContainer" type="VBoxContainer" parent="VBoxContainer/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
[node name="ReplaceLabel" type="Label" parent="VBoxContainer/HBoxContainer/ReplaceContainer"]
layout_mode = 2
text = "Replace with:"
[node name="ReplaceInput" type="LineEdit" parent="VBoxContainer/HBoxContainer/ReplaceContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
clear_button_enabled = true
[node name="ReplaceToolbar" type="HBoxContainer" parent="VBoxContainer/HBoxContainer/ReplaceContainer"]
layout_mode = 2
[node name="ReplaceSelectedButton" type="Button" parent="VBoxContainer/HBoxContainer/ReplaceContainer/ReplaceToolbar"]
unique_name_in_owner = true
layout_mode = 2
text = "Replace selected"
[node name="ReplaceAllButton" type="Button" parent="VBoxContainer/HBoxContainer/ReplaceContainer/ReplaceToolbar"]
unique_name_in_owner = true
layout_mode = 2
text = "Replace all"
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="ReplaceToolbar" type="HBoxContainer" parent="VBoxContainer/VBoxContainer"]
layout_mode = 2
[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
follow_focus = true
[node name="ResultsContainer" type="VBoxContainer" parent="VBoxContainer/ScrollContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_constants/separation = 0
[node name="ResultTemplate" type="HBoxContainer" parent="."]
unique_name_in_owner = true
layout_mode = 0
offset_left = 155.0
offset_top = -74.0
offset_right = 838.0
offset_bottom = -51.0
[node name="CheckBox" type="CheckBox" parent="ResultTemplate"]
layout_mode = 2
[node name="Label" type="RichTextLabel" parent="ResultTemplate"]
layout_mode = 2
size_flags_horizontal = 3
focus_mode = 2
theme_override_styles/focus = SubResource("StyleBoxFlat_owohg")
bbcode_enabled = true
text = "Result"
fit_content = true
scroll_active = false
[connection signal="text_submitted" from="VBoxContainer/HBoxContainer/FindContainer/Input" to="." method="_on_input_text_submitted"]
[connection signal="pressed" from="VBoxContainer/HBoxContainer/FindContainer/FindToolbar/SearchButton" to="." method="_on_search_button_pressed"]
[connection signal="toggled" from="VBoxContainer/HBoxContainer/FindContainer/FindToolbar/MatchCaseButton" to="." method="_on_match_case_button_toggled"]
[connection signal="toggled" from="VBoxContainer/HBoxContainer/FindContainer/FindToolbar/ReplaceToggle" to="." method="_on_replace_toggle_toggled"]
[connection signal="text_changed" from="VBoxContainer/HBoxContainer/ReplaceContainer/ReplaceInput" to="." method="_on_replace_input_text_changed"]
[connection signal="pressed" from="VBoxContainer/HBoxContainer/ReplaceContainer/ReplaceToolbar/ReplaceSelectedButton" to="." method="_on_replace_selected_button_pressed"]
[connection signal="pressed" from="VBoxContainer/HBoxContainer/ReplaceContainer/ReplaceToolbar/ReplaceAllButton" to="." method="_on_replace_all_button_pressed"]

View File

@ -0,0 +1,10 @@
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 = ""

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,15 @@
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

View File

@ -0,0 +1,10 @@
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

View File

@ -0,0 +1,212 @@
@tool
extends VBoxContainer
signal open_requested()
signal close_requested()
const DialogueConstants = preload("../constants.gd")
@onready var input: LineEdit = $Search/Input
@onready var result_label: Label = $Search/ResultLabel
@onready var previous_button: Button = $Search/PreviousButton
@onready var next_button: Button = $Search/NextButton
@onready var match_case_button: CheckBox = $Search/MatchCaseCheckBox
@onready var replace_check_button: CheckButton = $Search/ReplaceCheckButton
@onready var replace_panel: HBoxContainer = $Replace
@onready var replace_input: LineEdit = $Replace/Input
@onready var replace_button: Button = $Replace/ReplaceButton
@onready var replace_all_button: Button = $Replace/ReplaceAllButton
# The code edit we will be affecting (for some reason exporting this didn't work)
var code_edit: CodeEdit:
set(next_code_edit):
code_edit = next_code_edit
code_edit.gui_input.connect(_on_text_edit_gui_input)
code_edit.text_changed.connect(_on_text_edit_text_changed)
get:
return code_edit
var results: Array = []
var result_index: int = -1:
set(next_result_index):
result_index = next_result_index
if results.size() > 0:
var r = results[result_index]
code_edit.set_caret_line(r[0])
code_edit.select(r[0], r[1], r[0], r[1] + r[2])
else:
result_index = -1
if is_instance_valid(code_edit):
code_edit.deselect()
result_label.text = DialogueConstants.translate(&"n_of_n").format({ index = result_index + 1, total = results.size() })
get:
return result_index
func _ready() -> void:
apply_theme()
input.placeholder_text = DialogueConstants.translate(&"search.placeholder")
previous_button.tooltip_text = DialogueConstants.translate(&"search.previous")
next_button.tooltip_text = DialogueConstants.translate(&"search.next")
match_case_button.text = DialogueConstants.translate(&"search.match_case")
$Search/ReplaceCheckButton.text = DialogueConstants.translate(&"search.toggle_replace")
replace_button.text = DialogueConstants.translate(&"search.replace")
replace_all_button.text = DialogueConstants.translate(&"search.replace_all")
$Replace/ReplaceLabel.text = DialogueConstants.translate(&"search.replace_with")
self.result_index = -1
replace_panel.hide()
replace_button.disabled = true
replace_all_button.disabled = true
hide()
func focus_line_edit() -> void:
input.grab_focus()
input.select_all()
func apply_theme() -> void:
if is_instance_valid(previous_button):
previous_button.icon = get_theme_icon("ArrowLeft", "EditorIcons")
if is_instance_valid(next_button):
next_button.icon = get_theme_icon("ArrowRight", "EditorIcons")
# Find text in the code
func search(text: String = "", default_result_index: int = 0) -> void:
results.clear()
if text == "":
text = input.text
var lines = code_edit.text.split("\n")
for line_number in range(0, lines.size()):
var line = lines[line_number]
var column = find_in_line(line, text, 0)
while column > -1:
results.append([line_number, column, text.length()])
column = find_in_line(line, text, column + 1)
if results.size() > 0:
replace_button.disabled = false
replace_all_button.disabled = false
else:
replace_button.disabled = true
replace_all_button.disabled = true
self.result_index = clamp(default_result_index, 0, results.size() - 1)
# Find text in a string and match case if requested
func find_in_line(line: String, text: String, from_index: int = 0) -> int:
if match_case_button.button_pressed:
return line.find(text, from_index)
else:
return line.findn(text, from_index)
### Signals
func _on_text_edit_gui_input(event: InputEvent) -> void:
if event is InputEventKey and event.is_pressed():
match event.as_text():
"Ctrl+F", "Command+F":
open_requested.emit()
get_viewport().set_input_as_handled()
"Ctrl+Shift+R", "Command+Shift+R":
replace_check_button.set_pressed(true)
open_requested.emit()
get_viewport().set_input_as_handled()
func _on_text_edit_text_changed() -> void:
results.clear()
func _on_search_and_replace_theme_changed() -> void:
apply_theme()
func _on_input_text_changed(new_text: String) -> void:
search(new_text)
func _on_previous_button_pressed() -> void:
self.result_index = wrapi(result_index - 1, 0, results.size())
func _on_next_button_pressed() -> void:
self.result_index = wrapi(result_index + 1, 0, results.size())
func _on_search_and_replace_visibility_changed() -> void:
if is_instance_valid(input):
if visible:
input.grab_focus()
var selection = code_edit.get_selected_text()
if input.text == "" and selection != "":
input.text = selection
search(selection)
else:
search()
else:
input.text = ""
func _on_input_gui_input(event: InputEvent) -> void:
if event is InputEventKey and event.is_pressed():
match event.as_text():
"Enter":
search(input.text)
"Escape":
emit_signal("close_requested")
func _on_replace_button_pressed() -> void:
if result_index == -1: return
# Replace the selection at result index
var r: Array = results[result_index]
var lines: PackedStringArray = code_edit.text.split("\n")
var line: String = lines[r[0]]
line = line.substr(0, r[1]) + replace_input.text + line.substr(r[1] + r[2])
lines[r[0]] = line
code_edit.text = "\n".join(lines)
search(input.text, result_index)
code_edit.text_changed.emit()
func _on_replace_all_button_pressed() -> void:
if match_case_button.button_pressed:
code_edit.text = code_edit.text.replace(input.text, replace_input.text)
else:
code_edit.text = code_edit.text.replacen(input.text, replace_input.text)
search()
code_edit.text_changed.emit()
func _on_replace_check_button_toggled(button_pressed: bool) -> void:
replace_panel.visible = button_pressed
if button_pressed:
replace_input.grab_focus()
func _on_input_focus_entered() -> void:
if results.size() == 0:
search()
else:
self.result_index = result_index
func _on_match_case_check_box_toggled(button_pressed: bool) -> void:
search()

View File

@ -0,0 +1,87 @@
[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"]
[node name="SearchAndReplace" type="VBoxContainer"]
visible = false
anchors_preset = 10
anchor_right = 1.0
offset_bottom = 31.0
grow_horizontal = 2
size_flags_horizontal = 3
script = ExtResource("1_8oj1f")
[node name="Search" type="HBoxContainer" parent="."]
layout_mode = 2
[node name="Input" type="LineEdit" parent="Search"]
layout_mode = 2
size_flags_horizontal = 3
placeholder_text = "Text to search for"
metadata/_edit_use_custom_anchors = true
[node name="MatchCaseCheckBox" type="CheckBox" parent="Search"]
layout_mode = 2
text = "Match case"
[node name="VSeparator" type="VSeparator" parent="Search"]
layout_mode = 2
[node name="PreviousButton" type="Button" parent="Search"]
layout_mode = 2
tooltip_text = "Previous"
flat = true
[node name="ResultLabel" type="Label" parent="Search"]
layout_mode = 2
text = "0 of 0"
[node name="NextButton" type="Button" parent="Search"]
layout_mode = 2
tooltip_text = "Next"
flat = true
[node name="VSeparator2" type="VSeparator" parent="Search"]
layout_mode = 2
[node name="ReplaceCheckButton" type="CheckButton" parent="Search"]
layout_mode = 2
text = "Replace"
[node name="Replace" type="HBoxContainer" parent="."]
visible = false
layout_mode = 2
[node name="ReplaceLabel" type="Label" parent="Replace"]
layout_mode = 2
text = "Replace with:"
[node name="Input" type="LineEdit" parent="Replace"]
layout_mode = 2
size_flags_horizontal = 3
[node name="ReplaceButton" type="Button" parent="Replace"]
layout_mode = 2
disabled = true
text = "Replace"
flat = true
[node name="ReplaceAllButton" type="Button" parent="Replace"]
layout_mode = 2
disabled = true
text = "Replace all"
flat = true
[connection signal="theme_changed" from="." to="." method="_on_search_and_replace_theme_changed"]
[connection signal="visibility_changed" from="." to="." method="_on_search_and_replace_visibility_changed"]
[connection signal="focus_entered" from="Search/Input" to="." method="_on_input_focus_entered"]
[connection signal="gui_input" from="Search/Input" to="." method="_on_input_gui_input"]
[connection signal="text_changed" from="Search/Input" to="." method="_on_input_text_changed"]
[connection signal="toggled" from="Search/MatchCaseCheckBox" to="." method="_on_match_case_check_box_toggled"]
[connection signal="pressed" from="Search/PreviousButton" to="." method="_on_previous_button_pressed"]
[connection signal="pressed" from="Search/NextButton" to="." method="_on_next_button_pressed"]
[connection signal="toggled" from="Search/ReplaceCheckButton" to="." method="_on_replace_check_button_toggled"]
[connection signal="focus_entered" from="Replace/Input" to="." method="_on_input_focus_entered"]
[connection signal="gui_input" from="Replace/Input" to="." method="_on_input_gui_input"]
[connection signal="pressed" from="Replace/ReplaceButton" to="." method="_on_replace_button_pressed"]
[connection signal="pressed" from="Replace/ReplaceAllButton" to="." method="_on_replace_all_button_pressed"]

View File

@ -0,0 +1,67 @@
@tool
extends VBoxContainer
signal title_selected(title: String)
const DialogueConstants = preload("../constants.gd")
@onready var filter_edit: LineEdit = $FilterEdit
@onready var list: ItemList = $List
var titles: PackedStringArray:
set(next_titles):
titles = next_titles
apply_filter()
get:
return titles
var filter: String:
set(next_filter):
filter = next_filter
apply_filter()
get:
return filter
func _ready() -> void:
apply_theme()
filter_edit.placeholder_text = DialogueConstants.translate(&"titles_list.filter")
func select_title(title: String) -> void:
list.deselect_all()
for i in range(0, list.get_item_count()):
if list.get_item_text(i) == title.strip_edges():
list.select(i)
func apply_filter() -> void:
list.clear()
for title in titles:
if filter == "" or filter.to_lower() in title.to_lower():
list.add_item(title.strip_edges())
func apply_theme() -> void:
if is_instance_valid(filter_edit):
filter_edit.right_icon = get_theme_icon("Search", "EditorIcons")
### Signals
func _on_theme_changed() -> void:
apply_theme()
func _on_filter_edit_text_changed(new_text: String) -> void:
self.filter = new_text
func _on_list_item_clicked(index: int, at_position: Vector2, mouse_button_index: int) -> void:
if mouse_button_index == MOUSE_BUTTON_LEFT:
var title = list.get_item_text(index)
title_selected.emit(title)

View File

@ -0,0 +1,27 @@
[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"]
[node name="TitleList" type="VBoxContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 3
script = ExtResource("1_5qqmd")
[node name="FilterEdit" type="LineEdit" parent="."]
layout_mode = 2
placeholder_text = "Filter titles"
clear_button_enabled = true
[node name="List" type="ItemList" parent="."]
layout_mode = 2
size_flags_vertical = 3
allow_reselect = true
[connection signal="theme_changed" from="." to="." method="_on_theme_changed"]
[connection signal="text_changed" from="FilterEdit" to="." method="_on_filter_edit_text_changed"]
[connection signal="item_clicked" from="List" to="." method="_on_list_item_clicked"]

View File

@ -0,0 +1,125 @@
@tool
extends Button
const DialogueConstants = preload("../constants.gd")
const DialogueSettings = preload("../settings.gd")
const REMOTE_RELEASES_URL = "https://api.github.com/repos/nathanhoad/godot_dialogue_manager/releases"
@onready var http_request: HTTPRequest = $HTTPRequest
@onready var download_dialog: AcceptDialog = $DownloadDialog
@onready var download_update_panel = $DownloadDialog/DownloadUpdatePanel
@onready var needs_reload_dialog: AcceptDialog = $NeedsReloadDialog
@onready var update_failed_dialog: AcceptDialog = $UpdateFailedDialog
@onready var timer: Timer = $Timer
var needs_reload: bool = false
# A lambda that gets called just before refreshing the plugin. Return false to stop the reload.
var on_before_refresh: Callable = func(): return true
func _ready() -> void:
hide()
apply_theme()
# Check for updates on GitHub
check_for_update()
# Check again every few hours
timer.start(60 * 60 * 12)
# Convert a version number to an actually comparable number
func version_to_number(version: String) -> int:
var bits = version.split(".")
return bits[0].to_int() * 1000000 + bits[1].to_int() * 1000 + bits[2].to_int()
func apply_theme() -> void:
var color: Color = get_theme_color("success_color", "Editor")
if needs_reload:
color = get_theme_color("error_color", "Editor")
icon = get_theme_icon("Reload", "EditorIcons")
add_theme_color_override("icon_normal_color", color)
add_theme_color_override("icon_focus_color", color)
add_theme_color_override("icon_hover_color", color)
add_theme_color_override("font_color", color)
add_theme_color_override("font_focus_color", color)
add_theme_color_override("font_hover_color", color)
func check_for_update() -> void:
if DialogueSettings.get_user_value("check_for_updates", true):
http_request.request(REMOTE_RELEASES_URL)
### Signals
func _on_http_request_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void:
if result != HTTPRequest.RESULT_SUCCESS: return
var current_version: String = Engine.get_meta("DialogueManagerPlugin").get_version()
# Work out the next version from the releases information on GitHub
var response = JSON.parse_string(body.get_string_from_utf8())
if typeof(response) != TYPE_ARRAY: return
# GitHub releases are in order of creation, not order of version
var versions = (response as Array).filter(func(release):
var version: String = release.tag_name.substr(1)
var major_version: int = version.split(".")[0].to_int()
var current_major_version: int = current_version.split(".")[0].to_int()
return major_version == current_major_version and version_to_number(version) > version_to_number(current_version)
)
if versions.size() > 0:
download_update_panel.next_version_release = versions[0]
text = DialogueConstants.translate(&"update.available").format({ version = versions[0].tag_name.substr(1) })
show()
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)
else:
var scale: float = Engine.get_meta("DialogueManagerPlugin").get_editor_interface().get_editor_scale()
download_dialog.min_size = Vector2(300, 250) * scale
download_dialog.popup_centered()
func _on_download_dialog_close_requested() -> void:
download_dialog.hide()
func _on_download_update_panel_updated(updated_to_version: String) -> void:
download_dialog.hide()
needs_reload_dialog.dialog_text = DialogueConstants.translate(&"update.needs_reload")
needs_reload_dialog.ok_button_text = DialogueConstants.translate(&"update.reload_ok_button")
needs_reload_dialog.cancel_button_text = DialogueConstants.translate(&"update.reload_cancel_button")
needs_reload_dialog.popup_centered()
needs_reload = true
text = DialogueConstants.translate(&"update.reload_project")
apply_theme()
func _on_download_update_panel_failed() -> void:
download_dialog.hide()
update_failed_dialog.dialog_text = DialogueConstants.translate(&"update.failed")
update_failed_dialog.popup_centered()
func _on_needs_reload_dialog_confirmed() -> void:
Engine.get_meta("DialogueManagerPlugin").get_editor_interface().restart_editor(true)
func _on_timer_timeout() -> void:
if not needs_reload:
check_for_update()

View File

@ -0,0 +1,42 @@
[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="PackedScene" uid="uid://qdxrxv3c3hxk" path="res://addons/dialogue_manager/components/download_update_panel.tscn" id="2_iwm7r"]
[node name="UpdateButton" type="Button"]
visible = false
offset_right = 8.0
offset_bottom = 8.0
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_colors/font_hover_color = Color(0, 0, 0, 1)
theme_override_colors/font_focus_color = Color(0, 0, 0, 1)
text = "v2.9.0 available"
flat = true
script = ExtResource("1_d2tpb")
[node name="HTTPRequest" type="HTTPRequest" parent="."]
[node name="DownloadDialog" type="AcceptDialog" parent="."]
title = "Download update"
size = Vector2i(400, 300)
unresizable = true
min_size = Vector2i(300, 250)
ok_button_text = "Close"
[node name="DownloadUpdatePanel" parent="DownloadDialog" instance=ExtResource("2_iwm7r")]
[node name="UpdateFailedDialog" type="AcceptDialog" parent="."]
dialog_text = "You have been updated to version 2.4.3"
[node name="NeedsReloadDialog" type="ConfirmationDialog" parent="."]
[node name="Timer" type="Timer" parent="."]
wait_time = 14400.0
[connection signal="pressed" from="." to="." method="_on_update_button_pressed"]
[connection signal="request_completed" from="HTTPRequest" to="." method="_on_http_request_request_completed"]
[connection signal="close_requested" from="DownloadDialog" to="." method="_on_download_dialog_close_requested"]
[connection signal="failed" from="DownloadDialog/DownloadUpdatePanel" to="." method="_on_download_update_panel_failed"]
[connection signal="updated" from="DownloadDialog/DownloadUpdatePanel" to="." method="_on_download_update_panel_updated"]
[connection signal="confirmed" from="NeedsReloadDialog" to="." method="_on_needs_reload_dialog_confirmed"]
[connection signal="timeout" from="Timer" to="." method="_on_timer_timeout"]

View File

@ -0,0 +1,189 @@
extends Node
const USER_CONFIG_PATH = "user://dialogue_manager_user_config.json"
const CACHE_PATH = "user://dialogue_manager_cache.json"
# Token types
const TOKEN_FUNCTION = &"function"
const TOKEN_DICTIONARY_REFERENCE = &"dictionary_reference"
const TOKEN_DICTIONARY_NESTED_REFERENCE = &"dictionary_nested_reference"
const TOKEN_GROUP = &"group"
const TOKEN_ARRAY = &"array"
const TOKEN_DICTIONARY = &"dictionary"
const TOKEN_PARENS_OPEN = &"parens_open"
const TOKEN_PARENS_CLOSE = &"parens_close"
const TOKEN_BRACKET_OPEN = &"bracket_open"
const TOKEN_BRACKET_CLOSE = &"bracket_close"
const TOKEN_BRACE_OPEN = &"brace_open"
const TOKEN_BRACE_CLOSE = &"brace_close"
const TOKEN_COLON = &"colon"
const TOKEN_COMPARISON = &"comparison"
const TOKEN_ASSIGNMENT = &"assignment"
const TOKEN_OPERATOR = &"operator"
const TOKEN_COMMA = &"comma"
const TOKEN_DOT = &"dot"
const TOKEN_CONDITION = &"condition"
const TOKEN_BOOL = &"bool"
const TOKEN_NOT = &"not"
const TOKEN_AND_OR = &"and_or"
const TOKEN_STRING = &"string"
const TOKEN_NUMBER = &"number"
const TOKEN_VARIABLE = &"variable"
const TOKEN_COMMENT = &"comment"
const TOKEN_ERROR = &"error"
# Line types
const TYPE_UNKNOWN = &"unknown"
const TYPE_RESPONSE = &"response"
const TYPE_TITLE = &"title"
const TYPE_CONDITION = &"condition"
const TYPE_MUTATION = &"mutation"
const TYPE_GOTO = &"goto"
const TYPE_DIALOGUE = &"dialogue"
const TYPE_ERROR = &"error"
const TYPE_ELSE = &"else"
# Line IDs
const ID_NULL = &""
const ID_ERROR = &"error"
const ID_ERROR_INVALID_TITLE = &"invalid title"
const ID_ERROR_TITLE_HAS_NO_BODY = &"title has no body"
const ID_END = &"end"
const ID_END_CONVERSATION = &"end!"
# Errors
const ERR_ERRORS_IN_IMPORTED_FILE = 100
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
const ERR_TITLE_REFERENCE_HAS_NO_CONTENT = 109
const ERR_INVALID_EXPRESSION = 110
const ERR_UNEXPECTED_CONDITION = 111
const ERR_DUPLICATE_ID = 112
const ERR_MISSING_ID = 113
const ERR_INVALID_INDENTATION = 114
const ERR_INVALID_CONDITION_INDENTATION = 115
const ERR_INCOMPLETE_EXPRESSION = 116
const ERR_INVALID_EXPRESSION_FOR_VALUE = 117
const ERR_UNKNOWN_LINE_SYNTAX = 118
const ERR_TITLE_BEGINS_WITH_NUMBER = 119
const ERR_UNEXPECTED_END_OF_EXPRESSION = 120
const ERR_UNEXPECTED_FUNCTION = 121
const ERR_UNEXPECTED_BRACKET = 122
const ERR_UNEXPECTED_CLOSING_BRACKET = 123
const ERR_MISSING_CLOSING_BRACKET = 124
const ERR_UNEXPECTED_OPERATOR = 125
const ERR_UNEXPECTED_COMMA = 126
const ERR_UNEXPECTED_COLON = 127
const ERR_UNEXPECTED_DOT = 128
const ERR_UNEXPECTED_BOOLEAN = 129
const ERR_UNEXPECTED_STRING = 130
const ERR_UNEXPECTED_NUMBER = 131
const ERR_UNEXPECTED_VARIABLE = 132
const ERR_INVALID_INDEX = 133
const ERR_UNEXPECTED_ASSIGNMENT = 134
const ERR_UNKNOWN_USING = 135
## Get the error message
static func get_error_message(error: int) -> String:
match error:
ERR_ERRORS_IN_IMPORTED_FILE:
return translate(&"errors.import_errors")
ERR_FILE_ALREADY_IMPORTED:
return translate(&"errors.already_imported")
ERR_DUPLICATE_IMPORT_NAME:
return translate(&"errors.duplicate_import")
ERR_EMPTY_TITLE:
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:
return translate(&"errors.invalid_title_number")
ERR_UNKNOWN_TITLE:
return translate(&"errors.unknown_title")
ERR_INVALID_TITLE_REFERENCE:
return translate(&"errors.jump_to_invalid_title")
ERR_TITLE_REFERENCE_HAS_NO_CONTENT:
return translate(&"errors.title_has_no_content")
ERR_INVALID_EXPRESSION:
return translate(&"errors.invalid_expression")
ERR_UNEXPECTED_CONDITION:
return translate(&"errors.unexpected_condition")
ERR_DUPLICATE_ID:
return translate(&"errors.duplicate_id")
ERR_MISSING_ID:
return translate(&"errors.missing_id")
ERR_INVALID_INDENTATION:
return translate(&"errors.invalid_indentation")
ERR_INVALID_CONDITION_INDENTATION:
return translate(&"errors.condition_has_no_content")
ERR_INCOMPLETE_EXPRESSION:
return translate(&"errors.incomplete_expression")
ERR_INVALID_EXPRESSION_FOR_VALUE:
return translate(&"errors.invalid_expression_for_value")
ERR_FILE_NOT_FOUND:
return translate(&"errors.file_not_found")
ERR_UNEXPECTED_END_OF_EXPRESSION:
return translate(&"errors.unexpected_end_of_expression")
ERR_UNEXPECTED_FUNCTION:
return translate(&"errors.unexpected_function")
ERR_UNEXPECTED_BRACKET:
return translate(&"errors.unexpected_bracket")
ERR_UNEXPECTED_CLOSING_BRACKET:
return translate(&"errors.unexpected_closing_bracket")
ERR_MISSING_CLOSING_BRACKET:
return translate(&"errors.missing_closing_bracket")
ERR_UNEXPECTED_OPERATOR:
return translate(&"errors.unexpected_operator")
ERR_UNEXPECTED_COMMA:
return translate(&"errors.unexpected_comma")
ERR_UNEXPECTED_COLON:
return translate(&"errors.unexpected_colon")
ERR_UNEXPECTED_DOT:
return translate(&"errors.unexpected_dot")
ERR_UNEXPECTED_BOOLEAN:
return translate(&"errors.unexpected_boolean")
ERR_UNEXPECTED_STRING:
return translate(&"errors.unexpected_string")
ERR_UNEXPECTED_NUMBER:
return translate(&"errors.unexpected_number")
ERR_UNEXPECTED_VARIABLE:
return translate(&"errors.unexpected_variable")
ERR_INVALID_INDEX:
return translate(&"errors.invalid_index")
ERR_UNEXPECTED_ASSIGNMENT:
return translate(&"errors.unexpected_assignment")
ERR_UNKNOWN_USING:
return translate(&"errors.unknown_using")
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 language: String = TranslationServer.get_tool_locale()
var translations_path: String = "%s/l10n/%s.po" % [base_path, language]
var fallback_translations_path: String = "%s/l10n/%s.po" % [base_path, TranslationServer.get_tool_locale().substr(0, 2)]
var en_translations_path: String = "%s/l10n/en.po" % base_path
var translations: Translation = load(translations_path if FileAccess.file_exists(translations_path) else (fallback_translations_path if FileAccess.file_exists(fallback_translations_path) else en_translations_path))
return translations.get_message(string)

View File

@ -0,0 +1,230 @@
@icon("./assets/icon.svg")
@tool
## A RichTextLabel specifically for use with [b]Dialogue Manager[/b] dialogue.
class_name DialogueLabel extends RichTextLabel
## Emitted for each letter typed out.
signal spoke(letter: String, letter_index: int, speed: float)
## Emitted when typing paused for a `[wait]`
signal paused_typing(duration: float)
## Emitted when the player skips the typing of dialogue.
signal skipped_typing()
## Emitted when typing finishes.
signal finished_typing()
# The action to press to skip typing.
@export var skip_action: StringName = &"ui_cancel"
## The speed with which the text types out.
@export var seconds_per_step: float = 0.02
## Automatically have a brief pause when these characters are encountered.
@export var pause_at_characters: String = ".?!"
## Don't auto pause if the character after the pause is one of these.
@export var skip_pause_at_character_if_followed_by: String = ")\""
## Don't auto pause after these abbreviations (only if "." is in `pause_at_characters`).[br]
## Abbreviations are limitted to 5 characters in length [br]
## Does not support multi-period abbreviations (ex. "p.m.")
@export var skip_pause_at_abbreviations: PackedStringArray = ["Mr", "Mrs", "Ms", "Dr", "etc", "eg", "ex"]
## The amount of time to pause when exposing a character present in `pause_at_characters`.
@export var seconds_per_pause_step: float = 0.3
var _already_mutated_indices: PackedInt32Array = []
## The current line of dialogue.
var dialogue_line:
set(next_dialogue_line):
dialogue_line = next_dialogue_line
custom_minimum_size = Vector2.ZERO
text = dialogue_line.text
get:
return dialogue_line
## Whether the label is currently typing itself out.
var is_typing: bool = false:
set(value):
var is_finished: bool = is_typing != value and value == false
is_typing = value
if is_finished:
finished_typing.emit()
get:
return is_typing
var _last_wait_index: int = -1
var _last_mutation_index: int = -1
var _waiting_seconds: float = 0
var _is_awaiting_mutation: bool = false
func _process(delta: float) -> void:
if self.is_typing:
# Type out text
if visible_ratio < 1:
# See if we are waiting
if _waiting_seconds > 0:
_waiting_seconds = _waiting_seconds - delta
# If we are no longer waiting then keep typing
if _waiting_seconds <= 0:
_type_next(delta, _waiting_seconds)
else:
# Make sure any mutations at the end of the line get run
_mutate_inline_mutations(get_total_character_count())
self.is_typing = false
func _unhandled_input(event: InputEvent) -> void:
# Note: this will no longer be reached if using Dialogue Manager > 2.32.2. To make skip handling
# simpler (so all of mouse/keyboard/joypad are together) it is now the responsibility of the
# dialogue balloon.
if self.is_typing and visible_ratio < 1 and InputMap.has_action(skip_action) and event.is_action_pressed(skip_action):
get_viewport().set_input_as_handled()
skip_typing()
## Start typing out the text
func type_out() -> void:
text = dialogue_line.text
visible_characters = 0
visible_ratio = 0
_waiting_seconds = 0
_last_wait_index = -1
_last_mutation_index = -1
_already_mutated_indices.clear()
self.is_typing = true
# Allow typing listeners a chance to connect
await get_tree().process_frame
if get_total_character_count() == 0:
self.is_typing = false
elif seconds_per_step == 0:
_mutate_remaining_mutations()
visible_characters = get_total_character_count()
self.is_typing = false
## Stop typing out the text and jump right to the end
func skip_typing() -> void:
_mutate_remaining_mutations()
visible_characters = get_total_character_count()
self.is_typing = false
skipped_typing.emit()
# Type out the next character(s)
func _type_next(delta: float, seconds_needed: float) -> void:
if _is_awaiting_mutation: return
if visible_characters == get_total_character_count():
return
if _last_mutation_index != visible_characters:
_last_mutation_index = visible_characters
_mutate_inline_mutations(visible_characters)
if _is_awaiting_mutation: return
var additional_waiting_seconds: float = _get_pause(visible_characters)
# Pause on characters like "."
if _should_auto_pause():
additional_waiting_seconds += seconds_per_pause_step
# Pause at literal [wait] directives
if _last_wait_index != visible_characters and additional_waiting_seconds > 0:
_last_wait_index = visible_characters
_waiting_seconds += additional_waiting_seconds
paused_typing.emit(_get_pause(visible_characters))
else:
visible_characters += 1
if visible_characters <= get_total_character_count():
spoke.emit(get_parsed_text()[visible_characters - 1], visible_characters - 1, _get_speed(visible_characters))
# See if there's time to type out some more in this frame
seconds_needed += seconds_per_step * (1.0 / _get_speed(visible_characters))
if seconds_needed > delta:
_waiting_seconds += seconds_needed
else:
_type_next(delta, seconds_needed)
# Get the pause for the current typing position if there is one
func _get_pause(at_index: int) -> float:
return dialogue_line.pauses.get(at_index, 0)
# Get the speed for the current typing position
func _get_speed(at_index: int) -> float:
var speed: float = 1
for index in dialogue_line.speeds:
if index > at_index:
return speed
speed = dialogue_line.speeds[index]
return speed
# Run any inline mutations that haven't been run yet
func _mutate_remaining_mutations() -> void:
for i in range(visible_characters, get_total_character_count() + 1):
_mutate_inline_mutations(i)
# Run any mutations at the current typing position
func _mutate_inline_mutations(index: int) -> void:
for inline_mutation in dialogue_line.inline_mutations:
# inline mutations are an array of arrays in the form of [character index, resolvable function]
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)
_is_awaiting_mutation = false
# Determine if the current autopause character at the cursor should qualify to pause typing.
func _should_auto_pause() -> bool:
if visible_characters == 0: return false
var parsed_text: String = get_parsed_text()
# Avoid outofbounds when the label auto-translates and the text changes to one shorter while typing out
# Note: visible characters can be larger than parsed_text after a translation event
if visible_characters >= parsed_text.length(): return false
# Ignore pause characters if they are next to a non-pause character
if parsed_text[visible_characters] in skip_pause_at_character_if_followed_by.split():
return false
# 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:
return false
# Ignore "." if it's used in an abbreviation
# Note: does NOT support multi-period abbreviations (ex. p.m.)
if "." in pause_at_characters and parsed_text[visible_characters - 1] == ".":
for abbreviation in skip_pause_at_abbreviations:
if visible_characters >= abbreviation.length():
var previous_characters: String = parsed_text.substr(visible_characters - abbreviation.length() - 1, abbreviation.length())
if previous_characters == abbreviation:
return false
# Ignore two non-"." characters next to each other
var other_pause_characters: PackedStringArray = pause_at_characters.replace(".", "").split()
if visible_characters > 1 and parsed_text[visible_characters - 1] in other_pause_characters and parsed_text[visible_characters] in other_pause_characters:
return false
return parsed_text[visible_characters - 1] in pause_at_characters.split()

View File

@ -0,0 +1,19 @@
[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"]
[node name="DialogueLabel" type="RichTextLabel"]
anchors_preset = 10
anchor_right = 1.0
grow_horizontal = 2
mouse_filter = 1
bbcode_enabled = true
fit_content = true
scroll_active = false
shortcut_keys_enabled = false
meta_underlined = false
hint_underlined = false
deselect_on_focus_loss_enabled = false
visible_characters_behavior = 1
script = ExtResource("1_cital")
skip_pause_at_abbreviations = PackedStringArray("Mr", "Mrs", "Ms", "Dr", "etc", "eg", "ex")

View File

@ -0,0 +1,98 @@
## A line of dialogue returned from [code]DialogueManager[/code].
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
## The next line ID after this line.
var next_id: String = ""
## The character name that is saying this line.
var character: String = ""
## A dictionary of variable replacements fo the character name. Generally for internal use only.
var character_replacements: Array[Dictionary] = []
## The dialogue being spoken.
var text: String = ""
## A dictionary of replacements for the text. Generally for internal use only.
var text_replacements: Array[Dictionary] = []
## The key to use for translating this line.
var translation_key: String = ""
## A map for when and for how long to pause while typing out the dialogue text.
var pauses: Dictionary = {}
## A map for speed changes when typing out the dialogue text.
var speeds: Dictionary = {}
## A map of any mutations to run while typing out the dialogue text.
var inline_mutations: Array[Array] = []
## A list of responses attached to this line of dialogue.
var responses: Array = []
## A list of any extra game states to check when resolving variables and mutations.
var extra_game_states: Array = []
## How long to show this line before advancing to the next. Either a float (of seconds), [code]"auto"[/code], or [code]null[/code].
var time: String = ""
## Any #tags that were included in the line
var tags: PackedStringArray = []
## The mutation details if this is a mutation line (where [code]type == TYPE_MUTATION[/code]).
var mutation: Dictionary = {}
## The conditions to check before including this line in the flow of dialogue. If failed the line will be skipped over.
var conditions: Dictionary = {}
func _init(data: Dictionary = {}) -> void:
if data.size() > 0:
id = data.id
next_id = data.next_id
type = data.type
extra_game_states = data.get("extra_game_states", [])
match type:
_DialogueConstants.TYPE_DIALOGUE:
character = data.character
character_replacements = data.get("character_replacements", [] as Array[Dictionary])
text = data.text
text_replacements = data.get("text_replacements", [] as Array[Dictionary])
translation_key = data.get("translation_key", data.text)
pauses = data.get("pauses", {})
speeds = data.get("speeds", {})
inline_mutations = data.get("inline_mutations", [] as Array[Array])
time = data.get("time", "")
tags = data.get("tags", [])
_DialogueConstants.TYPE_MUTATION:
mutation = data.mutation
func _to_string() -> String:
match type:
_DialogueConstants.TYPE_DIALOGUE:
return "<DialogueLine character=\"%s\" text=\"%s\">" % [character, text]
_DialogueConstants.TYPE_MUTATION:
return "<DialogueLine mutation>"
return ""
func get_tag_value(tag_name: String) -> String:
var wrapped := "%s=" % tag_name
for t in tags:
if t.begins_with(wrapped):
return t.replace(wrapped, "").strip_edges()
return ""

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,141 @@
@icon("./assets/responses_menu.svg")
## A [Container] for dialogue responses provided by [b]Dialogue Manager[/b].
class_name DialogueResponsesMenu extends Container
## Emitted when a response is selected.
signal response_selected(response)
## Optionally specify a control to duplicate for each response
@export var response_template: Control
## The action for accepting a response (is possibly overridden by parent dialogue balloon).
@export var next_action: StringName = &""
## The list of dialogue responses.
var responses: Array = []:
get:
return responses
set(value):
responses = value
# Remove any current items
for item in get_children():
if item == response_template: continue
remove_child(item)
item.queue_free()
# Add new items
if responses.size() > 0:
for response in responses:
var item: Control
if is_instance_valid(response_template):
item = response_template.duplicate(DUPLICATE_GROUPS | DUPLICATE_SCRIPTS | DUPLICATE_SIGNALS)
item.show()
else:
item = Button.new()
item.name = "Response%d" % get_child_count()
if not response.is_allowed:
item.name = String(item.name) + "Disallowed"
item.disabled = true
# If the item has a response property then use that
if "response" in item:
item.response = response
# Otherwise assume we can just set the text
else:
item.text = response.text
item.set_meta("response", response)
add_child(item)
_configure_focus()
func _ready() -> void:
visibility_changed.connect(func():
if visible and get_menu_items().size() > 0:
get_menu_items()[0].grab_focus()
)
if is_instance_valid(response_template):
response_template.hide()
## Get the selectable items in the menu.
func get_menu_items() -> Array:
var items: Array = []
for child in get_children():
if not child.visible: continue
if "Disallowed" in child.name: continue
items.append(child)
return items
## [b]DEPRECATED[/b]. Do not use.
func set_responses(next_responses: Array) -> void:
self.responses = next_responses
#region Internal
# Prepare the menu for keyboard and mouse navigation.
func _configure_focus() -> void:
var items = get_menu_items()
for i in items.size():
var item: Control = items[i]
item.focus_mode = Control.FOCUS_ALL
item.focus_neighbor_left = item.get_path()
item.focus_neighbor_right = item.get_path()
if i == 0:
item.focus_neighbor_top = item.get_path()
item.focus_previous = item.get_path()
else:
item.focus_neighbor_top = items[i - 1].get_path()
item.focus_previous = items[i - 1].get_path()
if i == items.size() - 1:
item.focus_neighbor_bottom = item.get_path()
item.focus_next = item.get_path()
else:
item.focus_neighbor_bottom = items[i + 1].get_path()
item.focus_next = items[i + 1].get_path()
item.mouse_entered.connect(_on_response_mouse_entered.bind(item))
item.gui_input.connect(_on_response_gui_input.bind(item, item.get_meta("response")))
items[0].grab_focus()
#endregion
#region Signals
func _on_response_mouse_entered(item: Control) -> void:
if "Disallowed" in item.name: return
item.grab_focus()
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:
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():
response_selected.emit(response)
#endregion

View File

@ -0,0 +1,43 @@
@tool
@icon("./assets/icon.svg")
## A collection of dialogue lines for use with [code]DialogueManager[/code].
class_name DialogueResource extends Resource
const _DialogueManager = preload("./dialogue_manager.gd")
const DialogueLine = preload("./dialogue_line.gd")
## A list of state shortcuts
@export var using_states: PackedStringArray = []
## A map of titles and the lines they point to.
@export var titles: Dictionary = {}
## A list of character names.
@export var character_names: PackedStringArray = []
## The first title in the file.
@export var first_title: String = ""
## A map of the encoded lines of dialogue.
@export var lines: Dictionary = {}
## raw version of the text
@export var raw_text: String
## 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:
return await Engine.get_singleton("DialogueManager").get_next_dialogue_line(self, title, extra_game_states, mutation_behaviour)
## Get the list of any titles found in the file.
func get_titles() -> PackedStringArray:
return titles.keys()
func _to_string() -> String:
return "<DialogueResource titles=\"%s\">" % [",".join(titles.keys())]

View File

@ -0,0 +1,62 @@
## A response to a line of dialogue, usualy attached to a [code]DialogueLine[/code].
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
## The next line ID to use if this response is selected by the player.
var next_id: String = ""
## [code]true[/code] if the condition of this line was met.
var is_allowed: bool = true
## A character (depending on the "characters in responses" behaviour setting).
var character: String = ""
## A dictionary of varialbe replaces for the character name. Generally for internal use only.
var character_replacements: Array[Dictionary] = []
## The prompt for this response.
var text: String = ""
## A dictionary of variable replaces for the text. Generally for internal use only.
var text_replacements: Array[Dictionary] = []
## Any #tags
var tags: PackedStringArray = []
## The key to use for translating the text.
var translation_key: String = ""
func _init(data: Dictionary = {}) -> void:
if data.size() > 0:
id = data.id
type = data.type
next_id = data.next_id
is_allowed = data.is_allowed
character = data.character
character_replacements = data.character_replacements
text = data.text
text_replacements = data.text_replacements
tags = data.tags
translation_key = data.translation_key
func _to_string() -> String:
return "<DialogueResponse text=\"%s\">" % text
func get_tag_value(tag_name: String) -> String:
var wrapped := "%s=" % tag_name
for t in tags:
if t.begins_with(wrapped):
return t.replace(wrapped, "").strip_edges()
return ""

View File

@ -0,0 +1,44 @@
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")
func _parse_file(path: String, msgids: Array, msgids_context_plural: Array) -> void:
var file: FileAccess = FileAccess.open(path, FileAccess.READ)
var text: String = file.get_as_text()
var data: DialogueManagerParseResult = DialogueManagerParser.parse_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 character_name in known_keys: continue
known_keys.append(character_name)
msgids_context_plural.append([character_name.replace('"', '\\"'), "dialogue", ""])
# Add all dialogue lines and responses
var dialogue: Dictionary = data.lines
for key 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
known_keys.append(line.translation_key)
if line.translation_key == "" or line.translation_key == line.text:
msgids_context_plural.append([line.text.replace('"', '\\"'), "", ""])
else:
msgids_context_plural.append([line.text.replace('"', '\\"'), line.translation_key.replace('"', '\\"'), ""])
func _get_recognized_extensions() -> PackedStringArray:
return ["dialogue"]

View File

@ -0,0 +1,219 @@
using Godot;
using Godot.Collections;
namespace DialogueManagerRuntime
{
public partial class ExampleBalloon : CanvasLayer
{
[Export] public string NextAction = "ui_accept";
[Export] public string SkipAction = "ui_cancel";
Control balloon;
RichTextLabel characterLabel;
RichTextLabel dialogueLabel;
VBoxContainer responsesMenu;
Resource resource;
Array<Variant> temporaryGameStates = new Array<Variant>();
bool isWaitingForInput = false;
bool willHideBalloon = false;
DialogueLine dialogueLine;
DialogueLine DialogueLine
{
get => dialogueLine;
set
{
isWaitingForInput = false;
balloon.FocusMode = Control.FocusModeEnum.All;
balloon.GrabFocus();
if (value == null)
{
QueueFree();
return;
}
dialogueLine = value;
UpdateDialogue();
}
}
public override void _Ready()
{
balloon = GetNode<Control>("%Balloon");
characterLabel = GetNode<RichTextLabel>("%CharacterLabel");
dialogueLabel = GetNode<RichTextLabel>("%DialogueLabel");
responsesMenu = GetNode<VBoxContainer>("%ResponsesMenu");
balloon.Hide();
balloon.GuiInput += (@event) =>
{
if ((bool)dialogueLabel.Get("is_typing"))
{
bool mouseWasClicked = @event is InputEventMouseButton && (@event as InputEventMouseButton).ButtonIndex == MouseButton.Left && @event.IsPressed();
bool skipButtonWasPressed = @event.IsActionPressed(SkipAction);
if (mouseWasClicked || skipButtonWasPressed)
{
GetViewport().SetInputAsHandled();
dialogueLabel.Call("skip_typing");
return;
}
}
if (!isWaitingForInput) return;
if (dialogueLine.Responses.Count > 0) return;
GetViewport().SetInputAsHandled();
if (@event is InputEventMouseButton && @event.IsPressed() && (@event as InputEventMouseButton).ButtonIndex == MouseButton.Left)
{
Next(dialogueLine.NextId);
}
else if (@event.IsActionPressed(NextAction) && GetViewport().GuiGetFocusOwner() == balloon)
{
Next(dialogueLine.NextId);
}
};
if (string.IsNullOrEmpty((string)responsesMenu.Get("next_action")))
{
responsesMenu.Set("next_action", NextAction);
}
responsesMenu.Connect("response_selected", Callable.From((DialogueResponse response) =>
{
Next(response.NextId);
}));
DialogueManager.Mutated += OnMutated;
}
public override void _ExitTree()
{
DialogueManager.Mutated -= OnMutated;
}
public override void _UnhandledInput(InputEvent @event)
{
// Only the balloon is allowed to handle input while it's showing
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)
{
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>();
isWaitingForInput = false;
resource = dialogueResource;
DialogueLine = await DialogueManager.GetNextDialogueLine(resource, title, temporaryGameStates);
}
public async void Next(string nextId)
{
DialogueLine = await DialogueManager.GetNextDialogueLine(resource, nextId, temporaryGameStates);
}
#region Helpers
private async void UpdateDialogue()
{
if (!IsNodeReady())
{
await ToSignal(this, SignalName.Ready);
}
// Set up the character name
characterLabel.Visible = !string.IsNullOrEmpty(dialogueLine.Character);
characterLabel.Text = Tr(dialogueLine.Character, "dialogue");
// Set up the dialogue
dialogueLabel.Hide();
dialogueLabel.Set("dialogue_line", dialogueLine);
// Set up the responses
responsesMenu.Hide();
responsesMenu.Set("responses", dialogueLine.Responses);
// Type out the text
balloon.Show();
willHideBalloon = false;
dialogueLabel.Show();
if (!string.IsNullOrEmpty(dialogueLine.Text))
{
dialogueLabel.Call("type_out");
await ToSignal(dialogueLabel, "finished_typing");
}
// Wait for input
if (dialogueLine.Responses.Count > 0)
{
balloon.FocusMode = Control.FocusModeEnum.None;
responsesMenu.Show();
}
else if (!string.IsNullOrEmpty(dialogueLine.Time))
{
float time = 0f;
if (!float.TryParse(dialogueLine.Time, out time))
{
time = dialogueLine.Text.Length * 0.02f;
}
await ToSignal(GetTree().CreateTimer(time), "timeout");
Next(dialogueLine.NextId);
}
else
{
isWaitingForInput = true;
balloon.FocusMode = Control.FocusModeEnum.All;
balloon.GrabFocus();
}
}
#endregion
#region signals
private void OnMutated(Dictionary _mutation)
{
isWaitingForInput = false;
willHideBalloon = true;
GetTree().CreateTimer(0.1f).Timeout += () =>
{
if (willHideBalloon)
{
willHideBalloon = false;
balloon.Hide();
}
};
}
#endregion
}
}

View File

@ -0,0 +1,156 @@
extends CanvasLayer
## The action to use for advancing the dialogue
@export var next_action: StringName = &"ui_accept"
## 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
## Temporary game states
var temporary_game_states: Array = []
## See if we are waiting for the player
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
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)
else:
is_waiting_for_input = true
balloon.focus_mode = Control.FOCUS_ALL
balloon.grab_focus()
get:
return dialogue_line
func _ready() -> void:
balloon.hide()
Engine.get_singleton("DialogueManager").mutated.connect(_on_mutated)
# If the responses menu doesn't have a next action set, use this one
if responses_menu.next_action.is_empty():
responses_menu.next_action = next_action
func _unhandled_input(_event: InputEvent) -> void:
# Only the balloon is allowed to handle input while it's showing
get_viewport().set_input_as_handled()
func _notification(what: int) -> void:
## Detect a change of locale and update the current dialogue line to show the new language
if what == NOTIFICATION_TRANSLATION_CHANGED and _locale != TranslationServer.get_locale() and is_instance_valid(dialogue_label):
_locale = TranslationServer.get_locale()
var visible_ratio = dialogue_label.visible_ratio
self.dialogue_line = await resource.get_next_dialogue_line(dialogue_line.id)
if visible_ratio < 1:
dialogue_label.skip_typing()
## Start some dialogue
func start(dialogue_resource: DialogueResource, title: String, extra_game_states: Array = []) -> void:
temporary_game_states = [self] + extra_game_states
is_waiting_for_input = false
resource = dialogue_resource
self.dialogue_line = await resource.get_next_dialogue_line(title, temporary_game_states)
## 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)
#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():
if will_hide_balloon:
will_hide_balloon = false
balloon.hide()
)
func _on_balloon_gui_input(event: InputEvent) -> void:
# See if we need to skip typing of the dialogue
if dialogue_label.is_typing:
var mouse_was_clicked: bool = event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed()
var skip_button_was_pressed: bool = event.is_action_pressed(skip_action)
if mouse_was_clicked or skip_button_was_pressed:
get_viewport().set_input_as_handled()
dialogue_label.skip_typing()
return
if not is_waiting_for_input: return
if dialogue_line.responses.size() > 0: return
# When there are no response options the balloon itself is the clickable thing
get_viewport().set_input_as_handled()
if event is InputEventMouseButton and event.is_pressed() and event.button_index == MOUSE_BUTTON_LEFT:
next(dialogue_line.next_id)
elif event.is_action_pressed(next_action) and get_viewport().gui_get_focus_owner() == balloon:
next(dialogue_line.next_id)
func _on_responses_menu_response_selected(response: DialogueResponse) -> void:
next(response.next_id)
#endregion

View File

@ -0,0 +1,149 @@
[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="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"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_spyqn"]
bg_color = Color(0, 0, 0, 1)
border_width_left = 3
border_width_top = 3
border_width_right = 3
border_width_bottom = 3
border_color = Color(0.329412, 0.329412, 0.329412, 1)
corner_radius_top_left = 5
corner_radius_top_right = 5
corner_radius_bottom_right = 5
corner_radius_bottom_left = 5
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ri4m3"]
bg_color = Color(0.121569, 0.121569, 0.121569, 1)
border_width_left = 3
border_width_top = 3
border_width_right = 3
border_width_bottom = 3
border_color = Color(1, 1, 1, 1)
corner_radius_top_left = 5
corner_radius_top_right = 5
corner_radius_bottom_right = 5
corner_radius_bottom_left = 5
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_e0njw"]
bg_color = Color(0, 0, 0, 1)
border_width_left = 3
border_width_top = 3
border_width_right = 3
border_width_bottom = 3
border_color = Color(0.6, 0.6, 0.6, 1)
corner_radius_top_left = 5
corner_radius_top_right = 5
corner_radius_bottom_right = 5
corner_radius_bottom_left = 5
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_uy0d5"]
bg_color = Color(0, 0, 0, 1)
border_width_left = 3
border_width_top = 3
border_width_right = 3
border_width_bottom = 3
corner_radius_top_left = 5
corner_radius_top_right = 5
corner_radius_bottom_right = 5
corner_radius_bottom_left = 5
[sub_resource type="Theme" id="Theme_qq3yp"]
default_font_size = 20
Button/styles/disabled = SubResource("StyleBoxFlat_spyqn")
Button/styles/focus = SubResource("StyleBoxFlat_ri4m3")
Button/styles/hover = SubResource("StyleBoxFlat_e0njw")
Button/styles/normal = SubResource("StyleBoxFlat_e0njw")
MarginContainer/constants/margin_bottom = 15
MarginContainer/constants/margin_left = 30
MarginContainer/constants/margin_right = 30
MarginContainer/constants/margin_top = 15
Panel/styles/panel = SubResource("StyleBoxFlat_uy0d5")
[node name="ExampleBalloon" type="CanvasLayer"]
layer = 100
script = ExtResource("1_36de5")
[node name="Balloon" type="Control" parent="."]
unique_name_in_owner = true
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
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
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 21.0
offset_top = -183.0
offset_right = -19.0
offset_bottom = -19.0
grow_horizontal = 2
grow_vertical = 0
mouse_filter = 1
[node name="Dialogue" type="MarginContainer" parent="Balloon/Panel"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="VBoxContainer" type="VBoxContainer" parent="Balloon/Panel/Dialogue"]
layout_mode = 2
[node name="CharacterLabel" type="RichTextLabel" parent="Balloon/Panel/Dialogue/VBoxContainer"]
unique_name_in_owner = true
modulate = Color(1, 1, 1, 0.501961)
layout_mode = 2
mouse_filter = 1
bbcode_enabled = true
text = "Character"
fit_content = true
scroll_active = false
[node name="DialogueLabel" parent="Balloon/Panel/Dialogue/VBoxContainer" instance=ExtResource("2_a8ve6")]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
text = "Dialogue..."
[node name="Responses" type="MarginContainer" parent="Balloon"]
layout_mode = 1
anchors_preset = 7
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -147.0
offset_top = -558.0
offset_right = 494.0
offset_bottom = -154.0
grow_horizontal = 2
grow_vertical = 0
[node name="ResponsesMenu" type="VBoxContainer" parent="Balloon/Responses" node_paths=PackedStringArray("response_template")]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 8
theme_override_constants/separation = 2
script = ExtResource("3_72ixx")
response_template = NodePath("ResponseExample")
[node name="ResponseExample" type="Button" parent="Balloon/Responses/ResponsesMenu"]
layout_mode = 2
text = "Response example"
[connection signal="gui_input" from="Balloon" to="." method="_on_balloon_gui_input"]
[connection signal="response_selected" from="Balloon/Responses/ResponsesMenu" to="." method="_on_responses_menu_response_selected"]

View File

@ -0,0 +1,173 @@
[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="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"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_235ry"]
content_margin_left = 6.0
content_margin_top = 3.0
content_margin_right = 6.0
content_margin_bottom = 3.0
bg_color = Color(0.0666667, 0.0666667, 0.0666667, 1)
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
border_color = Color(0.345098, 0.345098, 0.345098, 1)
corner_radius_top_left = 3
corner_radius_top_right = 3
corner_radius_bottom_right = 3
corner_radius_bottom_left = 3
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ufjut"]
content_margin_left = 6.0
content_margin_top = 3.0
content_margin_right = 6.0
content_margin_bottom = 3.0
bg_color = Color(0.227451, 0.227451, 0.227451, 1)
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
border_color = Color(1, 1, 1, 1)
corner_radius_top_left = 3
corner_radius_top_right = 3
corner_radius_bottom_right = 3
corner_radius_bottom_left = 3
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_fcbqo"]
content_margin_left = 6.0
content_margin_top = 3.0
content_margin_right = 6.0
content_margin_bottom = 3.0
bg_color = Color(0.0666667, 0.0666667, 0.0666667, 1)
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
corner_radius_top_left = 3
corner_radius_top_right = 3
corner_radius_bottom_right = 3
corner_radius_bottom_left = 3
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_t6i7a"]
content_margin_left = 6.0
content_margin_top = 3.0
content_margin_right = 6.0
content_margin_bottom = 3.0
bg_color = Color(0.0666667, 0.0666667, 0.0666667, 1)
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
corner_radius_top_left = 3
corner_radius_top_right = 3
corner_radius_bottom_right = 3
corner_radius_bottom_left = 3
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_uy0d5"]
bg_color = Color(0, 0, 0, 1)
border_width_left = 1
border_width_top = 1
border_width_right = 1
border_width_bottom = 1
corner_radius_top_left = 3
corner_radius_top_right = 3
corner_radius_bottom_right = 3
corner_radius_bottom_left = 3
[sub_resource type="Theme" id="Theme_qq3yp"]
default_font_size = 8
Button/styles/disabled = SubResource("StyleBoxFlat_235ry")
Button/styles/focus = SubResource("StyleBoxFlat_ufjut")
Button/styles/hover = SubResource("StyleBoxFlat_fcbqo")
Button/styles/normal = SubResource("StyleBoxFlat_t6i7a")
MarginContainer/constants/margin_bottom = 4
MarginContainer/constants/margin_left = 8
MarginContainer/constants/margin_right = 8
MarginContainer/constants/margin_top = 4
Panel/styles/panel = SubResource("StyleBoxFlat_uy0d5")
[node name="ExampleBalloon" type="CanvasLayer"]
layer = 100
script = ExtResource("1_s2gbs")
[node name="Balloon" type="Control" parent="."]
unique_name_in_owner = true
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
theme = SubResource("Theme_qq3yp")
[node name="Panel" type="Panel" parent="Balloon"]
layout_mode = 1
anchors_preset = 12
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 3.0
offset_top = -62.0
offset_right = -4.0
offset_bottom = -4.0
grow_horizontal = 2
grow_vertical = 0
[node name="Dialogue" type="MarginContainer" parent="Balloon/Panel"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="VBoxContainer" type="VBoxContainer" parent="Balloon/Panel/Dialogue"]
layout_mode = 2
[node name="CharacterLabel" type="RichTextLabel" parent="Balloon/Panel/Dialogue/VBoxContainer"]
unique_name_in_owner = true
modulate = Color(1, 1, 1, 0.501961)
layout_mode = 2
mouse_filter = 1
bbcode_enabled = true
text = "Character"
fit_content = true
scroll_active = false
[node name="DialogueLabel" parent="Balloon/Panel/Dialogue/VBoxContainer" instance=ExtResource("2_hfvdi")]
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
anchors_preset = 7
anchor_left = 0.5
anchor_top = 1.0
anchor_right = 0.5
anchor_bottom = 1.0
offset_left = -124.0
offset_top = -218.0
offset_right = 125.0
offset_bottom = -50.0
grow_horizontal = 2
grow_vertical = 0
[node name="ResponsesMenu" type="VBoxContainer" parent="Balloon/Responses"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 8
theme_override_constants/separation = 2
script = ExtResource("3_1j1j0")
[node name="ResponseExample" type="Button" parent="Balloon/Responses/ResponsesMenu"]
layout_mode = 2
text = "Response Example"
[connection signal="gui_input" from="Balloon" to="." method="_on_balloon_gui_input"]
[connection signal="response_selected" from="Balloon/Responses/ResponsesMenu" to="." method="_on_responses_menu_response_selected"]

View File

@ -0,0 +1,116 @@
@tool
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
func _get_importer_name() -> String:
# NOTE: A change to this forces a re-import of all dialogue
return "dialogue_manager_compiler_%s" % compiler_version
func _get_visible_name() -> String:
return "Dialogue"
func _get_import_order() -> int:
return -1000
func _get_priority() -> float:
return 1000.0
func _get_resource_type():
return "Resource"
func _get_recognized_extensions() -> PackedStringArray:
return PackedStringArray(["dialogue"])
func _get_save_extension():
return "tres"
func _get_preset_count() -> int:
return 0
func _get_preset_name(preset_index: int) -> String:
return "Unknown"
func _get_import_options(path: String, preset_index: int) -> Array:
# When the options array is empty there is a misleading error on export
# that actually means nothing so let's just have an invisible option.
return [{
name = "defaults",
default_value = true
}]
func _get_option_visibility(path: String, option_name: StringName, options: Dictionary) -> bool:
return false
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")
# Get the raw file contents
if not FileAccess.file_exists(source_file): return ERR_FILE_NOT_FOUND
var file: FileAccess = FileAccess.open(source_file, FileAccess.READ)
var raw_text: String = file.get_as_text()
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
# Get the current addon version
var config: ConfigFile = ConfigFile.new()
config.load("res://addons/dialogue_manager/plugin.cfg")
var version: String = config.get_value("plugin", "version")
# Save the results to a resource
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
# Clear errors and possibly trigger any cascade recompiles
cache.add_file(source_file, data)
err = ResourceSaver.save(resource, "%s.%s" % [save_path, _get_save_extension()])
compiled_resource.emit(resource)
# Recompile any dependencies
var dependent_paths: PackedStringArray = cache.get_dependent_paths_for_reimport(source_file)
for path in dependent_paths:
append_import_external_resource(path)
return err

View File

@ -0,0 +1,21 @@
@tool
extends EditorInspectorPlugin
const DialogueEditorProperty = preload("./components/editor_property/editor_property.gd")
func _can_handle(object) -> bool:
if object is GDScript: return false
if not object is Node: return false
if "name" in object and object.name == "Dialogue Manager": return false
return true
func _parse_property(object: Object, type, name: String, hint_type, hint_string: String, usage_flags: int, wide: bool) -> bool:
if hint_string == "DialogueResource" or ("dialogue" in name.to_lower() and hint_string == "Resource"):
var property_editor = DialogueEditorProperty.new()
add_property_editor(name, property_editor)
return true
return false

Binary file not shown.

View File

@ -0,0 +1,481 @@
msgid ""
msgstr ""
"Project-Id-Version: Dialogue Manager\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.2.2\n"
msgid "start_a_new_file"
msgstr "Start a new file"
msgid "open_a_file"
msgstr "Open a file"
msgid "open.open"
msgstr "Open..."
msgid "open.no_recent_files"
msgstr "No recent files"
msgid "open.clear_recent_files"
msgstr "Clear recent files"
msgid "save_all_files"
msgstr "Save all files"
msgid "find_in_files"
msgstr "Find in files..."
msgid "test_dialogue"
msgstr "Test dialogue"
msgid "search_for_text"
msgstr "Search for text"
msgid "insert"
msgstr "Insert"
msgid "translations"
msgstr "Translations"
msgid "settings"
msgstr "Settings"
msgid "sponsor"
msgstr "Sponsor"
msgid "show_support"
msgstr "Support Dialogue Manager"
msgid "docs"
msgstr "Docs"
msgid "insert.wave_bbcode"
msgstr "Wave BBCode"
msgid "insert.shake_bbcode"
msgstr "Shake BBCode"
msgid "insert.typing_pause"
msgstr "Typing pause"
msgid "insert.typing_speed_change"
msgstr "Typing speed change"
msgid "insert.auto_advance"
msgstr "Auto advance"
msgid "insert.templates"
msgstr "Templates"
msgid "insert.title"
msgstr "Title"
msgid "insert.dialogue"
msgstr "Dialogue"
msgid "insert.response"
msgstr "Response"
msgid "insert.random_lines"
msgstr "Random lines"
msgid "insert.random_text"
msgstr "Random text"
msgid "insert.actions"
msgstr "Actions"
msgid "insert.jump"
msgstr "Jump to title"
msgid "insert.end_dialogue"
msgstr "End dialogue"
msgid "generate_line_ids"
msgstr "Generate line IDs"
msgid "save_characters_to_csv"
msgstr "Save character names to CSV..."
msgid "save_to_csv"
msgstr "Save lines to CSV..."
msgid "import_from_csv"
msgstr "Import line changes from CSV..."
msgid "confirm_close"
msgstr "Save changes to '{path}'?"
msgid "confirm_close.save"
msgstr "Save changes"
msgid "confirm_close.discard"
msgstr "Discard"
msgid "buffer.save"
msgstr "Save"
msgid "buffer.save_as"
msgstr "Save as..."
msgid "buffer.close"
msgstr "Close"
msgid "buffer.close_all"
msgstr "Close all"
msgid "buffer.close_other_files"
msgstr "Close other files"
msgid "buffer.copy_file_path"
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}"
msgid "search.find"
msgstr "Find:"
msgid "search.find_all"
msgstr "Find all..."
msgid "search.placeholder"
msgstr "Text to search for"
msgid "search.replace_placeholder"
msgstr "Text to replace it with"
msgid "search.replace_selected"
msgstr "Replace selected"
msgid "search.previous"
msgstr "Previous"
msgid "search.next"
msgstr "Next"
msgid "search.match_case"
msgstr "Match case"
msgid "search.toggle_replace"
msgstr "Replace"
msgid "search.replace_with"
msgstr "Replace with:"
msgid "search.replace"
msgstr "Replace"
msgid "search.replace_all"
msgstr "Replace all"
msgid "files_list.filter"
msgstr "Filter files"
msgid "titles_list.filter"
msgstr "Filter titles"
msgid "errors.key_not_found"
msgstr "Key \"{key}\" not found."
msgid "errors.line_and_message"
msgstr "Error at {line}, {column}: {message}"
msgid "errors_in_script"
msgstr "You have errors in your script. Fix them and then try again."
msgid "errors_with_build"
msgstr "You need to fix dialogue errors before you can run your game."
msgid "errors.import_errors"
msgstr "There are errors in this imported file."
msgid "errors.already_imported"
msgstr "File already imported."
msgid "errors.duplicate_import"
msgstr "Duplicate import name."
msgid "errors.unknown_using"
msgstr "Unknown autoload in using statement."
msgid "errors.empty_title"
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."
msgid "errors.invalid_title_number"
msgstr "Titles cannot begin with a number."
msgid "errors.unknown_title"
msgstr "Unknown title."
msgid "errors.jump_to_invalid_title"
msgstr "This jump is pointing to an invalid title."
msgid "errors.title_has_no_content"
msgstr "That title has no content. Maybe change this to a \"=> END\"."
msgid "errors.invalid_expression"
msgstr "Expression is invalid."
msgid "errors.unexpected_condition"
msgstr "Unexpected condition."
msgid "errors.duplicate_id"
msgstr "This ID is already on another line."
msgid "errors.missing_id"
msgstr "This line is missing an ID."
msgid "errors.invalid_indentation"
msgstr "Invalid indentation."
msgid "errors.condition_has_no_content"
msgstr "A condition line needs an indented line below it."
msgid "errors.incomplete_expression"
msgstr "Incomplete expression."
msgid "errors.invalid_expression_for_value"
msgstr "Invalid expression for value."
msgid "errors.file_not_found"
msgstr "File not found."
msgid "errors.unexpected_end_of_expression"
msgstr "Unexpected end of expression."
msgid "errors.unexpected_function"
msgstr "Unexpected function."
msgid "errors.unexpected_bracket"
msgstr "Unexpected bracket."
msgid "errors.unexpected_closing_bracket"
msgstr "Unexpected closing bracket."
msgid "errors.missing_closing_bracket"
msgstr "Missing closing bracket."
msgid "errors.unexpected_operator"
msgstr "Unexpected operator."
msgid "errors.unexpected_comma"
msgstr "Unexpected comma."
msgid "errors.unexpected_colon"
msgstr "Unexpected colon."
msgid "errors.unexpected_dot"
msgstr "Unexpected dot."
msgid "errors.unexpected_boolean"
msgstr "Unexpected boolean."
msgid "errors.unexpected_string"
msgstr "Unexpected string."
msgid "errors.unexpected_number"
msgstr "Unexpected number."
msgid "errors.unexpected_variable"
msgstr "Unexpected variable."
msgid "errors.invalid_index"
msgstr "Invalid index."
msgid "errors.unexpected_assignment"
msgstr "Unexpected assignment."
msgid "errors.unknown"
msgstr "Unknown syntax."
msgid "update.available"
msgstr "v{version} available"
msgid "update.is_available_for_download"
msgstr "Version %s is available for download!"
msgid "update.downloading"
msgstr "Downloading..."
msgid "update.download_update"
msgstr "Download update"
msgid "update.needs_reload"
msgstr "The project needs to be reloaded to install the update."
msgid "update.reload_ok_button"
msgstr "Reload project"
msgid "update.reload_cancel_button"
msgstr "Do it later"
msgid "update.reload_project"
msgstr "Reload project"
msgid "update.release_notes"
msgstr "Read release notes"
msgid "update.success"
msgstr "Dialogue Manager is now v{version}."
msgid "update.failed"
msgstr "There was a problem downloading the update."
msgid "runtime.no_resource"
msgstr "No dialogue resource provided."
msgid "runtime.no_content"
msgstr "\"{file_path}\" has no content."
msgid "runtime.errors"
msgstr "You have {count} errors in your dialogue text."
msgid "runtime.error_detail"
msgstr "Line {line}: {message}"
msgid "runtime.errors_see_details"
msgstr "You have {count} errors in your dialogue text. See Output for details."
msgid "runtime.invalid_expression"
msgstr "\"{expression}\" is not a valid expression: {error}"
msgid "runtime.array_index_out_of_bounds"
msgstr "Index {index} out of bounds of array \"{array}\"."
msgid "runtime.left_hand_size_cannot_be_assigned_to"
msgstr "Left hand side of expression cannot be assigned to."
msgid "runtime.key_not_found"
msgstr "Key \"{key}\" not found in dictionary \"{dictionary}\""
msgid "runtime.property_not_found"
msgstr "\"{property}\" not found. States with directly referenceable properties/methods/signals include {states}. Autoloads need to be referenced by their name to use their properties."
msgid "runtime.property_not_found_missing_export"
msgstr "\"{property}\" not found. You might need to add an [Export] decorator. States with directly referenceable properties/methods/signals include {states}. Autoloads need to be referenced by their name to use their properties."
msgid "runtime.method_not_found"
msgstr "Method \"{method}\" not found. States with directly referenceable properties/methods/signals include {states}. Autoloads need to be referenced by their name to use their properties."
msgid "runtime.signal_not_found"
msgstr "Signal \"{signal_name}\" not found. States with directly referenceable properties/methods/signals include {states}. Autoloads need to be referenced by their name to use their properties."
msgid "runtime.method_not_callable"
msgstr "\"{method}\" is not a callable method on \"{object}\""
msgid "runtime.unknown_operator"
msgstr "Unknown operator."
msgid "runtime.unknown_autoload"
msgstr "\"{autoload}\" doesn't appear to be a valid autoload."
msgid "runtime.something_went_wrong"
msgstr "Something went wrong."
msgid "runtime.expected_n_got_n_args"
msgstr "\"{method}\" was called with {received} arguments but it only has {expected}."
msgid "runtime.unsupported_array_type"
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."

View File

@ -0,0 +1,457 @@
#
msgid ""
msgstr ""
"Project-Id-Version: Dialogue Manager\n"
"POT-Creation-Date: 2024-02-25 20:58\n"
"PO-Revision-Date: 2024-02-25 20:58\n"
"Last-Translator: you <you@example.com>\n"
"Language-Team: Spanish <yourteam@example.com>\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "start_a_new_file"
msgstr "Crear un nuevo archivo"
msgid "open_a_file"
msgstr "Abrir un archivo"
msgid "open.open"
msgstr "Abrir..."
msgid "open.no_recent_files"
msgstr "No hay archivos recientes"
msgid "open.clear_recent_files"
msgstr "Limpiar archivos recientes"
msgid "save_all_files"
msgstr "Guardar todos los archivos"
msgid "test_dialogue"
msgstr "Diálogo de prueba"
msgid "search_for_text"
msgstr "Buscar texto"
msgid "insert"
msgstr "Insertar"
msgid "translations"
msgstr "Traducciones"
msgid "settings"
msgstr "Ajustes"
msgid "show_support"
msgstr "Contribuye con Dialogue Manager"
msgid "docs"
msgstr "Docs"
msgid "insert.wave_bbcode"
msgstr "BBCode ondulado"
msgid "insert.shake_bbcode"
msgstr "BBCode agitado"
msgid "insert.typing_pause"
msgstr "Pausa de escritura"
msgid "insert.typing_speed_change"
msgstr "Cambiar la velocidad de escritura"
msgid "insert.auto_advance"
msgstr "Avance automático"
msgid "insert.templates"
msgstr "Plantillas"
msgid "insert.title"
msgstr "Título"
msgid "insert.dialogue"
msgstr "Diálogo"
msgid "insert.response"
msgstr "Respuesta"
msgid "insert.random_lines"
msgstr "Líneas aleatorias"
msgid "insert.random_text"
msgstr "Texto aleatorio"
msgid "insert.actions"
msgstr "Acciones"
msgid "insert.jump"
msgstr "Ir al título"
msgid "insert.end_dialogue"
msgstr "Finalizar diálogo"
msgid "generate_line_ids"
msgstr "Generar IDs de línea"
msgid "save_characters_to_csv"
msgstr "Guardar los nombres de los personajes en un archivo CSV..."
msgid "save_to_csv"
msgstr "Guardar líneas en CSV..."
msgid "import_from_csv"
msgstr "Importar cambios de línea desde CSV..."
msgid "confirm_close"
msgstr "¿Guardar los cambios en '{path}'?"
msgid "confirm_close.save"
msgstr "Guardar cambios"
msgid "confirm_close.discard"
msgstr "Descartar"
msgid "buffer.save"
msgstr "Guardar"
msgid "buffer.save_as"
msgstr "Guardar como..."
msgid "buffer.close"
msgstr "Cerrar"
msgid "buffer.close_all"
msgstr "Cerrar todo"
msgid "buffer.close_other_files"
msgstr "Cerrar otros archivos"
msgid "buffer.copy_file_path"
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}"
msgid "search.previous"
msgstr "Anterior"
msgid "search.next"
msgstr "Siguiente"
msgid "search.match_case"
msgstr "Coincidir mayúsculas/minúsculas"
msgid "search.toggle_replace"
msgstr "Reemplazar"
msgid "search.replace_with"
msgstr "Reemplazar con:"
msgid "search.replace"
msgstr "Reemplazar"
msgid "search.replace_all"
msgstr "Reemplazar todo"
msgid "files_list.filter"
msgstr "Filtrar archivos"
msgid "titles_list.filter"
msgstr "Filtrar títulos"
msgid "errors.key_not_found"
msgstr "La tecla \"{key}\" no se encuentra."
msgid "errors.line_and_message"
msgstr "Error en {line}, {column}: {message}"
msgid "errors_in_script"
msgstr "Tienes errores en tu guion. Corrígelos y luego inténtalo de nuevo."
msgid "errors_with_build"
msgstr "Debes corregir los errores de diálogo antes de poder ejecutar tu juego."
msgid "errors.import_errors"
msgstr "Hay errores en este archivo importado."
msgid "errors.already_imported"
msgstr "Archivo ya importado."
msgid "errors.duplicate_import"
msgstr "Nombre de importación duplicado."
msgid "errors.unknown_using"
msgstr "Autoload desconocida en la declaración de uso."
msgid "errors.empty_title"
msgstr "Los títulos no pueden estar vacíos."
msgid "errors.duplicate_title"
msgstr "Ya hay un título con ese nombre."
msgid "errors.nested_title"
msgstr "Los títulos no pueden tener sangría."
msgid "errors.invalid_title_string"
msgstr "Los títulos solo pueden contener caracteres alfanuméricos y números."
msgid "errors.invalid_title_number"
msgstr "Los títulos no pueden empezar con un número."
msgid "errors.unknown_title"
msgstr "Título desconocido."
msgid "errors.jump_to_invalid_title"
msgstr "Este salto está apuntando a un título inválido."
msgid "errors.title_has_no_content"
msgstr "Ese título no tiene contenido. Quizá cambiarlo a \"=> FIN\"."
msgid "errors.invalid_expression"
msgstr "La expresión es inválida."
msgid "errors.unexpected_condition"
msgstr "Condición inesperada."
msgid "errors.duplicate_id"
msgstr "Este ID ya está en otra línea."
msgid "errors.missing_id"
msgstr "Esta línea está sin ID."
msgid "errors.invalid_indentation"
msgstr "Sangría no válida."
msgid "errors.condition_has_no_content"
msgstr "Una línea de condición necesita una línea sangrada debajo de ella."
msgid "errors.incomplete_expression"
msgstr "Expresión incompleta."
msgid "errors.invalid_expression_for_value"
msgstr "Expresión no válida para valor."
msgid "errors.file_not_found"
msgstr "Archivo no encontrado."
msgid "errors.unexpected_end_of_expression"
msgstr "Fin de expresión inesperado."
msgid "errors.unexpected_function"
msgstr "Función inesperada."
msgid "errors.unexpected_bracket"
msgstr "Corchete inesperado."
msgid "errors.unexpected_closing_bracket"
msgstr "Bracket de cierre inesperado."
msgid "errors.missing_closing_bracket"
msgstr "Falta cerrar corchete."
msgid "errors.unexpected_operator"
msgstr "Operador inesperado."
msgid "errors.unexpected_comma"
msgstr "Coma inesperada."
msgid "errors.unexpected_colon"
msgstr "Dos puntos inesperados"
msgid "errors.unexpected_dot"
msgstr "Punto inesperado."
msgid "errors.unexpected_boolean"
msgstr "Booleano inesperado."
msgid "errors.unexpected_string"
msgstr "String inesperado."
msgid "errors.unexpected_number"
msgstr "Número inesperado."
msgid "errors.unexpected_variable"
msgstr "Variable inesperada."
msgid "errors.invalid_index"
msgstr "Índice no válido."
msgid "errors.unexpected_assignment"
msgstr "Asignación inesperada."
msgid "errors.unknown"
msgstr "Sintaxis desconocida."
msgid "update.available"
msgstr "v{version} disponible"
msgid "update.is_available_for_download"
msgstr "¡La versión %s ya está disponible para su descarga!"
msgid "update.downloading"
msgstr "Descargando..."
msgid "update.download_update"
msgstr "Descargar actualización"
msgid "update.needs_reload"
msgstr "El proyecto debe ser recargado para instalar la actualización."
msgid "update.reload_ok_button"
msgstr "Recargar proyecto"
msgid "update.reload_cancel_button"
msgstr "Hazlo más tarde"
msgid "update.reload_project"
msgstr "Recargar proyecto"
msgid "update.release_notes"
msgstr "Leer las notas de la versión"
msgid "update.success"
msgstr "El Gestor de Diálogo ahora es v{versión}."
msgid "update.failed"
msgstr "Hubo un problema al descargar la actualización."
msgid "runtime.no_resource"
msgstr "Recurso de diálogo no proporcionado."
msgid "runtime.no_content"
msgstr "\"{file_path}\" no tiene contenido."
msgid "runtime.errors"
msgstr "Tienes {count} errores en tu diálogo de texto."
msgid "runtime.error_detail"
msgstr "Línea {line}: {message}"
msgid "runtime.errors_see_details"
msgstr "Tienes {count} errores en tu texto de diálogo. Consulta la salida para más detalles."
msgid "runtime.invalid_expression"
msgstr "\"{expression}\" no es una expresión válida: {error}"
msgid "runtime.array_index_out_of_bounds"
msgstr "Índice {index} fuera de los límites del array \"{array}\"."
msgid "runtime.left_hand_size_cannot_be_assigned_to"
msgstr "El lado izquierdo de la expresión no se puede asignar."
msgid "runtime.key_not_found"
msgstr "Clave \"{key}\" no encontrada en el diccionario \"{dictionary}\""
msgid "runtime.property_not_found"
msgstr "\"{property}\" no es una propiedad en ningún estado del juego ({states})."
msgid "runtime.property_not_found_missing_export"
msgstr "\"{property}\" no es una propiedad en ningún estado del juego ({states}). Es posible que necesites añadir un decorador [Export]."
msgid "runtime.method_not_found"
msgstr "\"{method}\" no es un método en ningún estado del juego ({states})"
msgid "runtime.signal_not_found"
msgstr "\"{signal_name}\" no es una señal en ningún estado del juego ({states})"
msgid "runtime.method_not_callable"
msgstr "\"{method}\" no es un método llamable en \"{object}\""
msgid "runtime.unknown_operator"
msgstr "Operador desconocido."
msgid "runtime.unknown_autoload"
msgstr "\"{autoload}\" parece no ser un autoload válido."
msgid "runtime.something_went_wrong"
msgstr "Algo salió mal."
msgid "runtime.expected_n_got_n_args"
msgstr "El método \"{method}\" se llamó con {received} argumentos, pero solo tiene {expected}."
msgid "runtime.unsupported_array_type"
msgstr "Array[{type}] no está soportado en mutaciones. Utiliza Array como tipo en su lugar."
msgid "runtime.dialogue_balloon_missing_start_method"
msgstr "Tu globo de diálogo no tiene un método \"start\" o \"Start\"."

View File

@ -0,0 +1,471 @@
msgid ""
msgstr ""
"Project-Id-Version: Dialogue Manager\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8-bit\n"
msgid "start_a_new_file"
msgstr ""
msgid "open_a_file"
msgstr ""
msgid "open.open"
msgstr ""
msgid "open.no_recent_files"
msgstr ""
msgid "open.clear_recent_files"
msgstr ""
msgid "save_all_files"
msgstr ""
msgid "find_in_files"
msgstr ""
msgid "test_dialogue"
msgstr ""
msgid "search_for_text"
msgstr ""
msgid "insert"
msgstr ""
msgid "translations"
msgstr ""
msgid "settings"
msgstr ""
msgid "sponsor"
msgstr ""
msgid "show_support"
msgstr ""
msgid "docs"
msgstr ""
msgid "insert.wave_bbcode"
msgstr ""
msgid "insert.shake_bbcode"
msgstr ""
msgid "insert.typing_pause"
msgstr ""
msgid "insert.typing_speed_change"
msgstr ""
msgid "insert.auto_advance"
msgstr ""
msgid "insert.templates"
msgstr ""
msgid "insert.title"
msgstr ""
msgid "insert.dialogue"
msgstr ""
msgid "insert.response"
msgstr ""
msgid "insert.random_lines"
msgstr ""
msgid "insert.random_text"
msgstr ""
msgid "insert.actions"
msgstr ""
msgid "insert.jump"
msgstr ""
msgid "insert.end_dialogue"
msgstr ""
msgid "generate_line_ids"
msgstr ""
msgid "save_to_csv"
msgstr ""
msgid "import_from_csv"
msgstr ""
msgid "confirm_close"
msgstr ""
msgid "confirm_close.save"
msgstr ""
msgid "confirm_close.discard"
msgstr ""
msgid "buffer.save"
msgstr ""
msgid "buffer.save_as"
msgstr ""
msgid "buffer.close"
msgstr ""
msgid "buffer.close_all"
msgstr ""
msgid "buffer.close_other_files"
msgstr ""
msgid "buffer.copy_file_path"
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 ""
msgid "search.find"
msgstr ""
msgid "search.find_all"
msgstr ""
msgid "search.placeholder"
msgstr ""
msgid "search.replace_placeholder"
msgstr ""
msgid "search.replace_selected"
msgstr ""
msgid "search.previous"
msgstr ""
msgid "search.next"
msgstr ""
msgid "search.match_case"
msgstr ""
msgid "search.toggle_replace"
msgstr ""
msgid "search.replace_with"
msgstr ""
msgid "search.replace"
msgstr ""
msgid "search.replace_all"
msgstr ""
msgid "files_list.filter"
msgstr ""
msgid "titles_list.filter"
msgstr ""
msgid "errors.key_not_found"
msgstr ""
msgid "errors.line_and_message"
msgstr ""
msgid "errors_in_script"
msgstr ""
msgid "errors_with_build"
msgstr ""
msgid "errors.import_errors"
msgstr ""
msgid "errors.already_imported"
msgstr ""
msgid "errors.duplicate_import"
msgstr ""
msgid "errors.unknown_using"
msgstr ""
msgid "errors.empty_title"
msgstr ""
msgid "errors.duplicate_title"
msgstr ""
msgid "errors.nested_title"
msgstr ""
msgid "errors.invalid_title_string"
msgstr ""
msgid "errors.invalid_title_number"
msgstr ""
msgid "errors.unknown_title"
msgstr ""
msgid "errors.jump_to_invalid_title"
msgstr ""
msgid "errors.title_has_no_content"
msgstr ""
msgid "errors.invalid_expression"
msgstr ""
msgid "errors.unexpected_condition"
msgstr ""
msgid "errors.duplicate_id"
msgstr ""
msgid "errors.missing_id"
msgstr ""
msgid "errors.invalid_indentation"
msgstr ""
msgid "errors.condition_has_no_content"
msgstr ""
msgid "errors.incomplete_expression"
msgstr ""
msgid "errors.invalid_expression_for_value"
msgstr ""
msgid "errors.file_not_found"
msgstr ""
msgid "errors.unexpected_end_of_expression"
msgstr ""
msgid "errors.unexpected_function"
msgstr ""
msgid "errors.unexpected_bracket"
msgstr ""
msgid "errors.unexpected_closing_bracket"
msgstr ""
msgid "errors.missing_closing_bracket"
msgstr ""
msgid "errors.unexpected_operator"
msgstr ""
msgid "errors.unexpected_comma"
msgstr ""
msgid "errors.unexpected_colon"
msgstr ""
msgid "errors.unexpected_dot"
msgstr ""
msgid "errors.unexpected_boolean"
msgstr ""
msgid "errors.unexpected_string"
msgstr ""
msgid "errors.unexpected_number"
msgstr ""
msgid "errors.unexpected_variable"
msgstr ""
msgid "errors.invalid_index"
msgstr ""
msgid "errors.unexpected_assignment"
msgstr ""
msgid "errors.unknown"
msgstr ""
msgid "update.available"
msgstr ""
msgid "update.is_available_for_download"
msgstr ""
msgid "update.downloading"
msgstr ""
msgid "update.download_update"
msgstr ""
msgid "update.needs_reload"
msgstr ""
msgid "update.reload_ok_button"
msgstr ""
msgid "update.reload_cancel_button"
msgstr ""
msgid "update.reload_project"
msgstr ""
msgid "update.release_notes"
msgstr ""
msgid "update.success"
msgstr ""
msgid "update.failed"
msgstr ""
msgid "runtime.no_resource"
msgstr ""
msgid "runtime.no_content"
msgstr ""
msgid "runtime.errors"
msgstr ""
msgid "runtime.error_detail"
msgstr ""
msgid "runtime.errors_see_details"
msgstr ""
msgid "runtime.invalid_expression"
msgstr ""
msgid "runtime.array_index_out_of_bounds"
msgstr ""
msgid "runtime.left_hand_size_cannot_be_assigned_to"
msgstr ""
msgid "runtime.key_not_found"
msgstr ""
msgid "runtime.property_not_found"
msgstr ""
msgid "runtime.property_not_found_missing_export"
msgstr ""
msgid "runtime.method_not_found"
msgstr ""
msgid "runtime.signal_not_found"
msgstr ""
msgid "runtime.method_not_callable"
msgstr ""
msgid "runtime.unknown_operator"
msgstr ""
msgid "runtime.unknown_autoload"
msgstr ""
msgid "runtime.something_went_wrong"
msgstr ""
msgid "runtime.expected_n_got_n_args"
msgstr ""
msgid "runtime.unsupported_array_type"
msgstr ""
msgid "runtime.dialogue_balloon_missing_start_method"
msgstr ""

View File

@ -0,0 +1,480 @@
msgid ""
msgstr ""
"Project-Id-Version: Dialogue Manager\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: Veydzher\n"
"Language: uk\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.2.2\n"
msgid "start_a_new_file"
msgstr "Створити новий файл"
msgid "open_a_file"
msgstr "Відкрити файл"
msgid "open.open"
msgstr "Відкрити..."
msgid "open.no_recent_files"
msgstr "Немає недавніх файлів"
msgid "open.clear_recent_files"
msgstr "Очистити недавні файли"
msgid "save_all_files"
msgstr "Зберегти всі файли"
msgid "find_in_files"
msgstr "Знайти у файліх..."
msgid "test_dialogue"
msgstr "Тестувати діалог"
msgid "search_for_text"
msgstr "Шукати текст"
msgid "insert"
msgstr "Вставити"
msgid "translations"
msgstr "Переклади"
msgid "settings"
msgstr "Налаштування"
msgid "sponsor"
msgstr "Спонсор"
msgid "show_support"
msgstr "Підтримка Dialogue Manager"
msgid "docs"
msgstr "Документація"
msgid "insert.wave_bbcode"
msgstr "Хвиля BBCode"
msgid "insert.shake_bbcode"
msgstr "Тряска BBCode"
msgid "insert.typing_pause"
msgstr "Пауза друку"
msgid "insert.typing_speed_change"
msgstr "Зміна швидкості друку"
msgid "insert.auto_advance"
msgstr "Автоматичне просування"
msgid "insert.templates"
msgstr "Шаблони"
msgid "insert.title"
msgstr "Заголовок"
msgid "insert.dialogue"
msgstr "Діалог"
msgid "insert.response"
msgstr "Відповідь"
msgid "insert.random_lines"
msgstr "Випадковий рядок"
msgid "insert.random_text"
msgstr "Випадковий текст"
msgid "insert.actions"
msgstr "Дії"
msgid "insert.jump"
msgstr "Перехід до заголовку"
msgid "insert.end_dialogue"
msgstr "Кінець діалогу"
msgid "generate_line_ids"
msgstr "Згенерувати ідентифікатори рядків"
msgid "save_characters_to_csv"
msgstr "Зберегти імена персонажів в CSV..."
msgid "save_to_csv"
msgstr "Зберегти рядки в CSV..."
msgid "import_from_csv"
msgstr "Імпортувати зміни в рядках з CSV..."
msgid "confirm_close"
msgstr "Зберегти зміни до '{path}'?"
msgid "confirm_close.save"
msgstr "Зберегти зміни"
msgid "confirm_close.discard"
msgstr "Скасувати"
msgid "buffer.save"
msgstr "Зберегти"
msgid "buffer.save_as"
msgstr "Зберегти як..."
msgid "buffer.close"
msgstr "Закрити"
msgid "buffer.close_all"
msgstr "Закрити все"
msgid "buffer.close_other_files"
msgstr "Закрити інші файли"
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 "Перевірити наявність оновлень"
msgid "n_of_n"
msgstr "{index} з {total}"
msgid "search.find"
msgstr "Знайти:"
msgid "search.find_all"
msgstr "Знайти всі..."
msgid "search.placeholder"
msgstr "Текст для пошуку"
msgid "search.replace_placeholder"
msgstr "Текст для заміни"
msgid "search.replace_selected"
msgstr "Замінити виділене"
msgid "search.previous"
msgstr "Назад"
msgid "search.next"
msgstr "Далі"
msgid "search.match_case"
msgstr "Збіг регістру"
msgid "search.toggle_replace"
msgstr "Замінити"
msgid "search.replace_with"
msgstr "Замінити на:"
msgid "search.replace"
msgstr "Замінити"
msgid "search.replace_all"
msgstr "Замінити все"
msgid "files_list.filter"
msgstr "Фільтр файлів"
msgid "titles_list.filter"
msgstr "Фільтр заголовків"
msgid "errors.key_not_found"
msgstr "Ключ «{key}» не знайдено."
msgid "errors.line_and_message"
msgstr "Помилка на {line}, {column}: {message}"
msgid "errors_in_script"
msgstr "У вашому скрипті є помилки. Виправте їх і спробуйте ще раз."
msgid "errors_with_build"
msgstr "Вам потрібно виправити помилки в діалогах, перш ніж ви зможете запустити гру."
msgid "errors.import_errors"
msgstr "В імпортованому файлі є помилки."
msgid "errors.already_imported"
msgstr "Файл уже імпортовано."
msgid "errors.duplicate_import"
msgstr "Дублювання назви імпорту."
msgid "errors.unknown_using"
msgstr "Невідоме автозавантаження в операторі використання."
msgid "errors.empty_title"
msgstr "Заголовки не можуть бути порожніми."
msgid "errors.duplicate_title"
msgstr "З такою назвою уже є заголовок."
msgid "errors.nested_title"
msgstr "Заголовки не повинні мати відступів."
msgid "errors.invalid_title_string"
msgstr "Заголовки можуть містити лише алфавітно-цифрові символи та цифри."
msgid "errors.invalid_title_number"
msgstr "Заголовки не можуть починатися з цифри."
msgid "errors.unknown_title"
msgstr "Невідомий заголовок."
msgid "errors.jump_to_invalid_title"
msgstr "Цей перехід вказує на недійсну назву."
msgid "errors.title_has_no_content"
msgstr "Цей заголовок не має змісту. Можливо, варто змінити його на «=> END»."
msgid "errors.invalid_expression"
msgstr "Вираз є недійсним."
msgid "errors.unexpected_condition"
msgstr "Несподівана умова."
msgid "errors.duplicate_id"
msgstr "Цей ідентифікатор вже на іншому рядку."
msgid "errors.missing_id"
msgstr "У цьому рядку відсутній ідентифікатор."
msgid "errors.invalid_indentation"
msgstr "Неправильний відступ."
msgid "errors.condition_has_no_content"
msgstr "Рядок умови потребує відступу під ним."
msgid "errors.incomplete_expression"
msgstr "Незавершений вираз."
msgid "errors.invalid_expression_for_value"
msgstr "Недійсний вираз для значення."
msgid "errors.file_not_found"
msgstr "Файл не знайдено."
msgid "errors.unexpected_end_of_expression"
msgstr "Несподіваний кінець виразу."
msgid "errors.unexpected_function"
msgstr "Несподівана функція."
msgid "errors.unexpected_bracket"
msgstr "Несподівана дужка."
msgid "errors.unexpected_closing_bracket"
msgstr "Несподівана закриваюча дужка."
msgid "errors.missing_closing_bracket"
msgstr "Відсутня закриваюча дужка."
msgid "errors.unexpected_operator"
msgstr "Несподіваний оператор."
msgid "errors.unexpected_comma"
msgstr "Несподівана кома."
msgid "errors.unexpected_colon"
msgstr "Несподівана двокрапка."
msgid "errors.unexpected_dot"
msgstr "Несподівана крапка."
msgid "errors.unexpected_boolean"
msgstr "Несподіваний логічний вираз."
msgid "errors.unexpected_string"
msgstr "Несподіваний рядок."
msgid "errors.unexpected_number"
msgstr "Несподіване число."
msgid "errors.unexpected_variable"
msgstr "Несподівана змінна."
msgid "errors.invalid_index"
msgstr "Недійсний індекс."
msgid "errors.unexpected_assignment"
msgstr "Несподіване призначення."
msgid "errors.unknown"
msgstr "Невідомий синтаксис."
msgid "update.available"
msgstr "Доступна версія {version}"
msgid "update.is_available_for_download"
msgstr "Версія %s доступна для завантаження!"
msgid "update.downloading"
msgstr "Завантаження..."
msgid "update.download_update"
msgstr "Завантажити оновлення"
msgid "update.needs_reload"
msgstr "Щоб встановити оновлення, проєкт потрібно перезавантажити."
msgid "update.reload_ok_button"
msgstr "Перезавантажити проєкт"
msgid "update.reload_cancel_button"
msgstr "Пізніше"
msgid "update.reload_project"
msgstr "Перезавантажити проєкт"
msgid "update.release_notes"
msgstr "Читати примітки оновлення"
msgid "update.success"
msgstr "Менеджер діалогів тепер має версію {version}."
msgid "update.failed"
msgstr "Виникла проблема із завантаженням оновлення."
msgid "runtime.no_resource"
msgstr "Ресурс для діалогу не надано."
msgid "runtime.no_content"
msgstr "«{file_path}» не має вмісту."
msgid "runtime.errors"
msgstr "У тексті діалогу було виявлено помилки ({count})."
msgid "runtime.error_detail"
msgstr "Рядок {line}: {message}"
msgid "runtime.errors_see_details"
msgstr "У тексті діалогу було виявлено помилки ({count}). Див. детальніше у розділі «Вивід»."
msgid "runtime.invalid_expression"
msgstr "«{expression}» не є допустимим виразом: {error}"
msgid "runtime.array_index_out_of_bounds"
msgstr "Індекс {index} виходить за межі масиву «{array}»."
msgid "runtime.left_hand_size_cannot_be_assigned_to"
msgstr "Ліва частина виразу не може бути присвоєна."
msgid "runtime.key_not_found"
msgstr "Ключ «{key}» у словнику «{dictionary}»"
msgid "runtime.property_not_found"
msgstr "«{property}» не є властивістю для жодного стану гри ({states})."
msgid "runtime.property_not_found_missing_export"
msgstr "«{property}» не є властивістю для жодного стану гри ({states}). Можливо, вам слід додати декоратор [Export]."
msgid "runtime.method_not_found"
msgstr "«{method}» не є методом на жодному зі станів гри ({states})"
msgid "runtime.signal_not_found"
msgstr "«{signal_name}» не є сигналом на жодному зі станів гри ({states})"
msgid "runtime.method_not_callable"
msgstr "«{method}» не є методом, який можна викликати в «{object}»"
msgid "runtime.unknown_operator"
msgstr "Невідомий оператор."
msgid "runtime.unknown_autoload"
msgstr "«{autoload}» не є дійсним автозавантаженням."
msgid "runtime.something_went_wrong"
msgstr "Щось пішло не так."
msgid "runtime.expected_n_got_n_args"
msgstr "«{method}» було викликано з аргументами «{received}», але він має лише «{expected}»."
msgid "runtime.unsupported_array_type"
msgstr "Array[{type}] не підтримується в мутаціях. Натомість використовуйте Array як тип."
msgid "runtime.dialogue_balloon_missing_start_method"
msgstr "У вашому діалоговому вікні відсутній метод «start» або «Start»."

View File

@ -0,0 +1,447 @@
msgid ""
msgstr ""
"Project-Id-Version: Dialogue Manager\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: penghao123456、憨憨羊の宇航鸽鸽、ABShinri\n"
"Language: zh\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.4\n"
msgid "start_a_new_file"
msgstr "创建新文件"
msgid "open_a_file"
msgstr "打开已有文件"
msgid "open.open"
msgstr "打开……"
msgid "open.no_recent_files"
msgstr "无历史记录"
msgid "open.clear_recent_files"
msgstr "清空历史记录"
msgid "save_all_files"
msgstr "保存所有文件"
msgid "find_in_files"
msgstr "在文件中查找"
msgid "test_dialogue"
msgstr "测试对话"
msgid "search_for_text"
msgstr "查找……"
msgid "insert"
msgstr "插入"
msgid "translations"
msgstr "翻译"
msgid "settings"
msgstr "设置"
msgid "show_support"
msgstr "支持 Dialogue Manager"
msgid "docs"
msgstr "文档"
msgid "insert.wave_bbcode"
msgstr "波浪效果"
msgid "insert.shake_bbcode"
msgstr "抖动效果"
msgid "insert.typing_pause"
msgstr "输入间隔"
msgid "insert.typing_speed_change"
msgstr "输入速度变更"
msgid "insert.auto_advance"
msgstr "自动切行"
msgid "insert.templates"
msgstr "模板"
msgid "insert.title"
msgstr "标题"
msgid "insert.dialogue"
msgstr "对话"
msgid "insert.response"
msgstr "回复选项"
msgid "insert.random_lines"
msgstr "随机行"
msgid "insert.random_text"
msgstr "随机文本"
msgid "insert.actions"
msgstr "操作"
msgid "insert.jump"
msgstr "标题间跳转"
msgid "insert.end_dialogue"
msgstr "结束对话"
msgid "generate_line_ids"
msgstr "生成行 ID"
msgid "save_characters_to_csv"
msgstr "保存角色到 CSV"
msgid "save_to_csv"
msgstr "生成 CSV"
msgid "import_from_csv"
msgstr "从 CSV 导入"
msgid "confirm_close"
msgstr "是否要保存到“{path}”?"
msgid "confirm_close.save"
msgstr "保存"
msgid "confirm_close.discard"
msgstr "不保存"
msgid "buffer.save"
msgstr "保存"
msgid "buffer.save_as"
msgstr "另存为……"
msgid "buffer.close"
msgstr "关闭"
msgid "buffer.close_all"
msgstr "全部关闭"
msgid "buffer.close_other_files"
msgstr "关闭其他文件"
msgid "buffer.copy_file_path"
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}个"
msgid "search.find"
msgstr "查找:"
msgid "search.find_all"
msgstr "查找全部..."
msgid "search.placeholder"
msgstr "请输入查找的内容"
msgid "search.replace_placeholder"
msgstr "请输入替换的内容"
msgid "search.replace_selected"
msgstr "替换勾选"
msgid "search.previous"
msgstr "查找上一个"
msgid "search.next"
msgstr "查找下一个"
msgid "search.match_case"
msgstr "大小写敏感"
msgid "search.toggle_replace"
msgstr "替换"
msgid "search.replace_with"
msgstr "替换为"
msgid "search.replace"
msgstr "替换"
msgid "search.replace_all"
msgstr "全部替换"
msgid "files_list.filter"
msgstr "查找文件"
msgid "titles_list.filter"
msgstr "查找标题"
msgid "errors.key_not_found"
msgstr "键“{key}”未找到"
msgid "errors.line_and_message"
msgstr "第{line}行第{colume}列发生错误:{message}"
msgid "errors_in_script"
msgstr "你的脚本中存在错误。请修复错误,然后重试。"
msgid "errors_with_build"
msgstr "请先解决 Dialogue 中的错误。"
msgid "errors.import_errors"
msgstr "被导入的文件存在问题。"
msgid "errors.already_imported"
msgstr "文件已被导入。"
msgid "errors.duplicate_import"
msgstr "导入名不能重复。"
msgid "errors.empty_title"
msgstr "标题名不能为空。"
msgid "errors.duplicate_title"
msgstr "标题名不能重复。"
msgid "errors.nested_title"
msgstr "标题不能嵌套。"
msgid "errors.invalid_title_string"
msgstr "标题名无效。"
msgid "errors.invalid_title_number"
msgstr "标题不能以数字开始。"
msgid "errors.unknown_title"
msgstr "标题未定义。"
msgid "errors.jump_to_invalid_title"
msgstr "标题名无效。"
msgid "errors.title_has_no_content"
msgstr "目标标题为空。请替换为“=> END”。"
msgid "errors.invalid_expression"
msgstr "表达式无效。"
msgid "errors.unexpected_condition"
msgstr "未知条件。"
msgid "errors.duplicate_id"
msgstr "ID 重复。"
msgid "errors.missing_id"
msgstr "ID 不存在。"
msgid "errors.invalid_indentation"
msgstr "缩进无效。"
msgid "errors.condition_has_no_content"
msgstr "条件下方不能为空。"
msgid "errors.incomplete_expression"
msgstr "不完整的表达式。"
msgid "errors.invalid_expression_for_value"
msgstr "无效的赋值表达式。"
msgid "errors.file_not_found"
msgstr "文件不存在。"
msgid "errors.unexpected_end_of_expression"
msgstr "表达式 end 不应存在。"
msgid "errors.unexpected_function"
msgstr "函数不应存在。"
msgid "errors.unexpected_bracket"
msgstr "方括号不应存在。"
msgid "errors.unexpected_closing_bracket"
msgstr "方括号不应存在。"
msgid "errors.missing_closing_bracket"
msgstr "闭方括号不存在。"
msgid "errors.unexpected_operator"
msgstr "操作符不应存在。"
msgid "errors.unexpected_comma"
msgstr "逗号不应存在。"
msgid "errors.unexpected_colon"
msgstr "冒号不应存在。"
msgid "errors.unexpected_dot"
msgstr "句号不应存在。"
msgid "errors.unexpected_boolean"
msgstr "布尔值不应存在。"
msgid "errors.unexpected_string"
msgstr "字符串不应存在。"
msgid "errors.unexpected_number"
msgstr "数字不应存在。"
msgid "errors.unexpected_variable"
msgstr "标识符不应存在。"
msgid "errors.invalid_index"
msgstr "索引无效。"
msgid "errors.unexpected_assignment"
msgstr "不应在条件判断中使用 = ,应使用 == 。"
msgid "errors.unknown"
msgstr "语法错误。"
msgid "update.available"
msgstr "v{version} 更新可用。"
msgid "update.is_available_for_download"
msgstr "v%s 已经可以下载。"
msgid "update.downloading"
msgstr "正在下载更新……"
msgid "update.download_update"
msgstr "下载"
msgid "update.needs_reload"
msgstr "需要重新加载项目以应用更新。"
msgid "update.reload_ok_button"
msgstr "重新加载"
msgid "update.reload_cancel_button"
msgstr "暂不重新加载"
msgid "update.reload_project"
msgstr "重新加载"
msgid "update.release_notes"
msgstr "查看发行注记"
msgid "update.success"
msgstr "v{version} 已成功安装并应用。"
msgid "update.failed"
msgstr "更新失败。"
msgid "runtime.no_resource"
msgstr "找不到资源。"
msgid "runtime.no_content"
msgstr "资源“{file_path}”为空。"
msgid "runtime.errors"
msgstr "文件中存在{errrors}个错误。"
msgid "runtime.error_detail"
msgstr "第{index}行:{message}"
msgid "runtime.errors_see_details"
msgstr "文件中存在{errrors}个错误。请查看调试输出。"
msgid "runtime.invalid_expression"
msgstr "表达式“{expression}”无效:{error}"
msgid "runtime.array_index_out_of_bounds"
msgstr "数组索引“{index}”越界。(数组名:“{array}”)"
msgid "runtime.left_hand_size_cannot_be_assigned_to"
msgstr "表达式左侧的变量无法被赋值。"
msgid "runtime.key_not_found"
msgstr "键“{key}”在字典“{dictionary}”中不存在。"
msgid "runtime.property_not_found"
msgstr "“{property}”不存在。(全局变量:{states}"
msgid "runtime.property_not_found_missing_export"
msgstr "“{property}”不存在。(全局变量:{states})你可能需要添加一个修饰词 [Export]。"
msgid "runtime.method_not_found"
msgstr "“{method}”不存在。(全局变量:{states}"
msgid "runtime.signal_not_found"
msgstr "“{sighal_name}”不存在。(全局变量:{states}"
msgid "runtime.method_not_callable"
msgstr "{method}不是对象“{object}”上的函数。"
msgid "runtime.unknown_operator"
msgstr "未知操作符。"
msgid "runtime.something_went_wrong"
msgstr "有什么出错了。"

View File

@ -0,0 +1,447 @@
msgid ""
msgstr ""
"Project-Id-Version: Dialogue Manager\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: 憨憨羊の宇航鴿鴿、ABShinri\n"
"Language: zh_TW\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.4\n"
msgid "start_a_new_file"
msgstr "創建新檔案"
msgid "open_a_file"
msgstr "開啟已有檔案"
msgid "open.open"
msgstr "開啟……"
msgid "open.no_recent_files"
msgstr "無歷史記錄"
msgid "open.clear_recent_files"
msgstr "清空歷史記錄"
msgid "save_all_files"
msgstr "儲存所有檔案"
msgid "find_in_files"
msgstr "在檔案中查找"
msgid "test_dialogue"
msgstr "測試對話"
msgid "search_for_text"
msgstr "搜尋……"
msgid "insert"
msgstr "插入"
msgid "translations"
msgstr "翻譯"
msgid "settings"
msgstr "設定"
msgid "show_support"
msgstr "支援 Dialogue Manager"
msgid "docs"
msgstr "文檔"
msgid "insert.wave_bbcode"
msgstr "波浪特效"
msgid "insert.shake_bbcode"
msgstr "震動特效"
msgid "insert.typing_pause"
msgstr "輸入間隔"
msgid "insert.typing_speed_change"
msgstr "輸入速度變更"
msgid "insert.auto_advance"
msgstr "自動切行"
msgid "insert.templates"
msgstr "模板"
msgid "insert.title"
msgstr "標題"
msgid "insert.dialogue"
msgstr "對話"
msgid "insert.response"
msgstr "回覆選項"
msgid "insert.random_lines"
msgstr "隨機行"
msgid "insert.random_text"
msgstr "隨機文本"
msgid "insert.actions"
msgstr "操作"
msgid "insert.jump"
msgstr "標題間跳轉"
msgid "insert.end_dialogue"
msgstr "結束對話"
msgid "generate_line_ids"
msgstr "生成行 ID"
msgid "save_characters_to_csv"
msgstr "保存角色到 CSV"
msgid "save_to_csv"
msgstr "生成 CSV"
msgid "import_from_csv"
msgstr "從 CSV 匯入"
msgid "confirm_close"
msgstr "是否要儲存到“{path}”?"
msgid "confirm_close.save"
msgstr "儲存"
msgid "confirm_close.discard"
msgstr "不儲存"
msgid "buffer.save"
msgstr "儲存"
msgid "buffer.save_as"
msgstr "儲存爲……"
msgid "buffer.close"
msgstr "關閉"
msgid "buffer.close_all"
msgstr "全部關閉"
msgid "buffer.close_other_files"
msgstr "關閉其他檔案"
msgid "buffer.copy_file_path"
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}個"
msgid "search.find"
msgstr "搜尋:"
msgid "search.find_all"
msgstr "搜尋全部..."
msgid "search.placeholder"
msgstr "請輸入搜尋的內容"
msgid "search.replace_placeholder"
msgstr "請輸入替換的內容"
msgid "search.replace_selected"
msgstr "替換勾選"
msgid "search.previous"
msgstr "搜尋上一個"
msgid "search.next"
msgstr "搜尋下一個"
msgid "search.match_case"
msgstr "大小寫敏感"
msgid "search.toggle_replace"
msgstr "替換"
msgid "search.replace_with"
msgstr "替換爲"
msgid "search.replace"
msgstr "替換"
msgid "search.replace_all"
msgstr "全部替換"
msgid "files_list.filter"
msgstr "搜尋檔案"
msgid "titles_list.filter"
msgstr "搜尋標題"
msgid "errors.key_not_found"
msgstr "鍵“{key}”未找到"
msgid "errors.line_and_message"
msgstr "第{line}行第{colume}列發生錯誤:{message}"
msgid "errors_in_script"
msgstr "你的腳本中存在錯誤。請修復錯誤,然後重試。"
msgid "errors_with_build"
msgstr "請先解決 Dialogue 中的錯誤。"
msgid "errors.import_errors"
msgstr "被匯入的檔案存在問題。"
msgid "errors.already_imported"
msgstr "檔案已被匯入。"
msgid "errors.duplicate_import"
msgstr "匯入名不能重複。"
msgid "errors.empty_title"
msgstr "標題名不能爲空。"
msgid "errors.duplicate_title"
msgstr "標題名不能重複。"
msgid "errors.nested_title"
msgstr "標題不能嵌套。"
msgid "errors.invalid_title_string"
msgstr "標題名無效。"
msgid "errors.invalid_title_number"
msgstr "標題不能以數字開始。"
msgid "errors.unknown_title"
msgstr "標題未定義。"
msgid "errors.jump_to_invalid_title"
msgstr "標題名無效。"
msgid "errors.title_has_no_content"
msgstr "目標標題爲空。請替換爲“=> END”。"
msgid "errors.invalid_expression"
msgstr "表達式無效。"
msgid "errors.unexpected_condition"
msgstr "未知條件。"
msgid "errors.duplicate_id"
msgstr "ID 重複。"
msgid "errors.missing_id"
msgstr "ID 不存在。"
msgid "errors.invalid_indentation"
msgstr "縮進無效。"
msgid "errors.condition_has_no_content"
msgstr "條件下方不能爲空。"
msgid "errors.incomplete_expression"
msgstr "不完整的表達式。"
msgid "errors.invalid_expression_for_value"
msgstr "無效的賦值表達式。"
msgid "errors.file_not_found"
msgstr "檔案不存在。"
msgid "errors.unexpected_end_of_expression"
msgstr "表達式 end 不應存在。"
msgid "errors.unexpected_function"
msgstr "函數不應存在。"
msgid "errors.unexpected_bracket"
msgstr "方括號不應存在。"
msgid "errors.unexpected_closing_bracket"
msgstr "方括號不應存在。"
msgid "errors.missing_closing_bracket"
msgstr "閉方括號不存在。"
msgid "errors.unexpected_operator"
msgstr "操作符不應存在。"
msgid "errors.unexpected_comma"
msgstr "逗號不應存在。"
msgid "errors.unexpected_colon"
msgstr "冒號不應存在。"
msgid "errors.unexpected_dot"
msgstr "句號不應存在。"
msgid "errors.unexpected_boolean"
msgstr "布爾值不應存在。"
msgid "errors.unexpected_string"
msgstr "字符串不應存在。"
msgid "errors.unexpected_number"
msgstr "數字不應存在。"
msgid "errors.unexpected_variable"
msgstr "標識符不應存在。"
msgid "errors.invalid_index"
msgstr "索引無效。"
msgid "errors.unexpected_assignment"
msgstr "不應在條件判斷中使用 = ,應使用 == 。"
msgid "errors.unknown"
msgstr "語法錯誤。"
msgid "update.available"
msgstr "v{version} 更新可用。"
msgid "update.is_available_for_download"
msgstr "v%s 已經可以下載。"
msgid "update.downloading"
msgstr "正在下載更新……"
msgid "update.download_update"
msgstr "下載"
msgid "update.needs_reload"
msgstr "需要重新加載項目以套用更新。"
msgid "update.reload_ok_button"
msgstr "重新加載"
msgid "update.reload_cancel_button"
msgstr "暫不重新加載"
msgid "update.reload_project"
msgstr "重新加載"
msgid "update.release_notes"
msgstr "查看發行註記"
msgid "update.success"
msgstr "v{version} 已成功安裝並套用。"
msgid "update.failed"
msgstr "更新失敗。"
msgid "runtime.no_resource"
msgstr "找不到資源。"
msgid "runtime.no_content"
msgstr "資源“{file_path}”爲空。"
msgid "runtime.errors"
msgstr "檔案中存在{errrors}個錯誤。"
msgid "runtime.error_detail"
msgstr "第{index}行:{message}"
msgid "runtime.errors_see_details"
msgstr "檔案中存在{errrors}個錯誤。請查看調試輸出。"
msgid "runtime.invalid_expression"
msgstr "表達式“{expression}”無效:{error}"
msgid "runtime.array_index_out_of_bounds"
msgstr "數組索引“{index}”越界。(數組名:“{array}”)"
msgid "runtime.left_hand_size_cannot_be_assigned_to"
msgstr "表達式左側的變量無法被賦值。"
msgid "runtime.key_not_found"
msgstr "鍵“{key}”在字典“{dictionary}”中不存在。"
msgid "runtime.property_not_found"
msgstr "“{property}”不存在。(全局變量:{states}"
msgid "runtime.method_not_found"
msgstr "“{method}”不存在。(全局變量:{states}"
msgid "runtime.signal_not_found"
msgstr "“{sighal_name}”不存在。(全局變量:{states}"
msgid "runtime.property_not_found_missing_export"
msgstr "“{property}”不存在。(全局變量:{states})你可能需要添加一個修飾詞 [Export]。"
msgid "runtime.method_not_callable"
msgstr "{method}不是對象“{object}”上的函數。"
msgid "runtime.unknown_operator"
msgstr "未知操作符。"
msgid "runtime.something_went_wrong"
msgstr "有什麼出錯了。"

View File

@ -0,0 +1,7 @@
[plugin]
name="Dialogue Manager"
description="A simple but powerful branching dialogue system"
author="Nathan Hoad"
version="2.41.4"
script="plugin.gd"

View File

@ -0,0 +1,363 @@
@tool
extends EditorPlugin
const DialogueConstants = preload("./constants.gd")
const DialogueImportPlugin = preload("./import_plugin.gd")
const DialogueInspectorPlugin = preload("./inspector_plugin.gd")
const DialogueTranslationParserPlugin = preload("./editor_translation_parser_plugin.gd")
const DialogueSettings = preload("./settings.gd")
const DialogueCache = preload("./components/dialogue_cache.gd")
const MainView = preload("./views/main_view.tscn")
const DialogueResource = preload("./dialogue_resource.gd")
var import_plugin: DialogueImportPlugin
var inspector_plugin: DialogueInspectorPlugin
var translation_parser_plugin: DialogueTranslationParserPlugin
var main_view
var dialogue_cache: DialogueCache
func _enter_tree() -> void:
add_autoload_singleton("DialogueManager", get_plugin_path() + "/dialogue_manager.gd")
if Engine.is_editor_hint():
Engine.set_meta("DialogueManagerPlugin", self)
DialogueSettings.prepare()
dialogue_cache = DialogueCache.new()
Engine.set_meta("DialogueCache", dialogue_cache)
import_plugin = DialogueImportPlugin.new()
add_import_plugin(import_plugin)
inspector_plugin = DialogueInspectorPlugin.new()
add_inspector_plugin(inspector_plugin)
translation_parser_plugin = DialogueTranslationParserPlugin.new()
add_translation_parser_plugin(translation_parser_plugin)
main_view = MainView.instantiate()
get_editor_interface().get_editor_main_screen().add_child(main_view)
_make_visible(false)
main_view.add_child(dialogue_cache)
_update_localization()
get_editor_interface().get_file_system_dock().files_moved.connect(_on_files_moved)
get_editor_interface().get_file_system_dock().file_removed.connect(_on_file_removed)
add_tool_menu_item("Create copy of dialogue example balloon...", _copy_dialogue_balloon)
# Prevent the project from showing as unsaved even though it was only just opened
if DialogueSettings.get_setting("try_suppressing_startup_unsaved_indicator", false) \
and Engine.get_physics_frames() == 0 \
and get_editor_interface().has_method("save_all_scenes"):
var timer: Timer = Timer.new()
var suppress_unsaved_marker: Callable
suppress_unsaved_marker = func():
if Engine.get_frames_per_second() >= 10:
timer.stop()
get_editor_interface().call("save_all_scenes")
timer.queue_free()
timer.timeout.connect(suppress_unsaved_marker)
add_child(timer)
timer.start(0.1)
func _exit_tree() -> void:
remove_autoload_singleton("DialogueManager")
remove_import_plugin(import_plugin)
import_plugin = null
remove_inspector_plugin(inspector_plugin)
inspector_plugin = null
remove_translation_parser_plugin(translation_parser_plugin)
translation_parser_plugin = null
if is_instance_valid(main_view):
main_view.queue_free()
Engine.remove_meta("DialogueManagerPlugin")
Engine.remove_meta("DialogueCache")
get_editor_interface().get_file_system_dock().files_moved.disconnect(_on_files_moved)
get_editor_interface().get_file_system_dock().file_removed.disconnect(_on_file_removed)
remove_tool_menu_item("Create copy of dialogue example balloon...")
func _has_main_screen() -> bool:
return true
func _make_visible(next_visible: bool) -> void:
if is_instance_valid(main_view):
main_view.visible = next_visible
func _get_plugin_name() -> String:
return "Dialogue"
func _get_plugin_icon() -> Texture2D:
return load(get_plugin_path() + "/assets/icon.svg")
func _handles(object) -> bool:
var editor_settings: EditorSettings = get_editor_interface().get_editor_settings()
var external_editor: String = editor_settings.get_setting("text_editor/external/exec_path")
var use_external_editor: bool = editor_settings.get_setting("text_editor/external/use_external_editor") and external_editor != ""
if object is DialogueResource and use_external_editor and DialogueSettings.get_user_value("open_in_external_editor", false):
var project_path: String = ProjectSettings.globalize_path("res://")
var file_path: String = ProjectSettings.globalize_path(object.resource_path)
OS.create_process(external_editor, [project_path, file_path])
return false
return object is DialogueResource
func _edit(object) -> void:
if is_instance_valid(main_view) and is_instance_valid(object):
main_view.open_resource(object)
func _apply_changes() -> void:
if is_instance_valid(main_view):
main_view.apply_changes()
_update_localization()
func _build() -> bool:
# If this is the dotnet Godot then we need to check if the solution file exists
DialogueSettings.check_for_dotnet_solution()
# Ignore errors in other files if we are just running the test scene
if DialogueSettings.get_user_value("is_running_test_scene", true): return true
if dialogue_cache != null:
var files_with_errors = dialogue_cache.get_files_with_errors()
if files_with_errors.size() > 0:
for dialogue_file in files_with_errors:
push_error("You have %d error(s) in %s" % [dialogue_file.errors.size(), dialogue_file.path])
get_editor_interface().edit_resource(load(files_with_errors[0].path))
main_view.show_build_error_dialog()
return false
return true
## Get the shortcuts used by the plugin
func get_editor_shortcuts() -> Dictionary:
var shortcuts: Dictionary = {
toggle_comment = [
_create_event("Ctrl+K"),
_create_event("Ctrl+Slash")
],
delete_line = [
_create_event("Ctrl+Shift+K")
],
move_up = [
_create_event("Alt+Up")
],
move_down = [
_create_event("Alt+Down")
],
save = [
_create_event("Ctrl+Alt+S")
],
close_file = [
_create_event("Ctrl+W")
],
find_in_files = [
_create_event("Ctrl+Shift+F")
],
run_test_scene = [
_create_event("Ctrl+F5")
],
text_size_increase = [
_create_event("Ctrl+Equal")
],
text_size_decrease = [
_create_event("Ctrl+Minus")
],
text_size_reset = [
_create_event("Ctrl+0")
]
}
var paths = get_editor_interface().get_editor_paths()
var settings
if FileAccess.file_exists(paths.get_config_dir() + "/editor_settings-4.3.tres"):
settings = load(paths.get_config_dir() + "/editor_settings-4.3.tres")
elif FileAccess.file_exists(paths.get_config_dir() + "/editor_settings-4.tres"):
settings = load(paths.get_config_dir() + "/editor_settings-4.tres")
else:
return shortcuts
for s in settings.get("shortcuts"):
for key in shortcuts:
if s.name == "script_text_editor/%s" % key or s.name == "script_editor/%s" % key:
shortcuts[key] = []
for event in s.shortcuts:
if event is InputEventKey:
shortcuts[key].append(event)
return shortcuts
func _create_event(string: String) -> InputEventKey:
var event: InputEventKey = InputEventKey.new()
var bits = string.split("+")
event.keycode = OS.find_keycode_from_string(bits[bits.size() - 1])
event.shift_pressed = bits.has("Shift")
event.alt_pressed = bits.has("Alt")
if bits.has("Ctrl") or bits.has("Command"):
event.command_or_control_autoremap = true
return event
## Get the editor shortcut that matches an event
func get_editor_shortcut(event: InputEventKey) -> String:
var shortcuts: Dictionary = get_editor_shortcuts()
for key in shortcuts:
for shortcut in shortcuts.get(key, []):
if event.as_text().split(" ")[0] == shortcut.as_text().split(" ")[0]:
return key
return ""
## Get the current version
func get_version() -> String:
var config: ConfigFile = ConfigFile.new()
config.load(get_plugin_path() + "/plugin.cfg")
return config.get_value("plugin", "version")
## Get the current path of the plugin
func get_plugin_path() -> String:
return get_script().resource_path.get_base_dir()
## Update references to a moved file
func update_import_paths(from_path: String, to_path: String) -> void:
dialogue_cache.move_file_path(from_path, to_path)
# Reopen the file if it's already open
if main_view.current_file_path == from_path:
if to_path == "":
main_view.close_file(from_path)
else:
main_view.current_file_path = ""
main_view.open_file(to_path)
# Update any other files that import the moved file
var dependents = dialogue_cache.get_files_with_dependency(from_path)
for dependent in dependents:
dependent.dependencies.remove_at(dependent.dependencies.find(from_path))
dependent.dependencies.append(to_path)
# Update the live buffer
if main_view.current_file_path == dependent.path:
main_view.code_edit.text = main_view.code_edit.text.replace(from_path, to_path)
main_view.pristine_text = main_view.code_edit.text
# Open the file and update the path
var file: FileAccess = FileAccess.open(dependent.path, FileAccess.READ)
var text = file.get_as_text().replace(from_path, to_path)
file.close()
file = FileAccess.open(dependent.path, FileAccess.WRITE)
file.store_string(text)
file.close()
func _update_localization() -> void:
var dialogue_files = dialogue_cache.get_files()
# Add any new files to POT generation
var files_for_pot: PackedStringArray = ProjectSettings.get_setting("internationalization/locale/translations_pot_files", [])
var files_for_pot_changed: bool = false
for path in dialogue_files:
if not files_for_pot.has(path):
files_for_pot.append(path)
files_for_pot_changed = true
# Remove any POT references that don't exist any more
for i in range(files_for_pot.size() - 1, -1, -1):
var file_for_pot: String = files_for_pot[i]
if file_for_pot.get_extension() == "dialogue" and not dialogue_files.has(file_for_pot):
files_for_pot.remove_at(i)
files_for_pot_changed = true
# Update project settings if POT changed
if files_for_pot_changed:
ProjectSettings.set_setting("internationalization/locale/translations_pot_files", files_for_pot)
ProjectSettings.save()
### Callbacks
func _copy_dialogue_balloon() -> void:
var scale: float = get_editor_interface().get_editor_scale()
var directory_dialog: FileDialog = FileDialog.new()
var label: Label = Label.new()
label.text = "Dialogue balloon files will be copied into chosen directory."
directory_dialog.get_vbox().add_child(label)
directory_dialog.file_mode = FileDialog.FILE_MODE_OPEN_DIR
directory_dialog.min_size = Vector2(600, 500) * scale
directory_dialog.dir_selected.connect(func(path):
var plugin_path: String = get_plugin_path()
var is_dotnet: bool = DialogueSettings.check_for_dotnet_solution()
var balloon_path: String = path + ("/Balloon.tscn" if is_dotnet else "/balloon.tscn")
var balloon_script_path: String = path + ("/DialogueBalloon.cs" if is_dotnet else "/balloon.gd")
# Copy the balloon scene file and change the script reference
var is_small_window: bool = ProjectSettings.get_setting("display/window/size/viewport_width") < 400
var example_balloon_file_name: String = "small_example_balloon.tscn" if is_small_window else "example_balloon.tscn"
var example_balloon_script_file_name: String = "ExampleBalloon.cs" if is_dotnet else "example_balloon.gd"
var file: FileAccess = FileAccess.open(plugin_path + "/example_balloon/" + example_balloon_file_name, FileAccess.READ)
var file_contents: String = file.get_as_text().replace(plugin_path + "/example_balloon/example_balloon.gd", balloon_script_path)
file = FileAccess.open(balloon_path, FileAccess.WRITE)
file.store_string(file_contents)
file.close()
# Copy the script file
file = FileAccess.open(plugin_path + "/example_balloon/" + example_balloon_script_file_name, FileAccess.READ)
file_contents = file.get_as_text()
if is_dotnet:
file_contents = file_contents.replace("class ExampleBalloon", "class DialogueBalloon")
file = FileAccess.open(balloon_script_path, FileAccess.WRITE)
file.store_string(file_contents)
file.close()
get_editor_interface().get_resource_filesystem().scan()
get_editor_interface().get_file_system_dock().call_deferred("navigate_to_path", balloon_path)
DialogueSettings.set_setting("balloon_path", balloon_path)
directory_dialog.queue_free()
)
get_editor_interface().get_base_control().add_child(directory_dialog)
directory_dialog.popup_centered()
### Signals
func _on_files_moved(old_file: String, new_file: String) -> void:
update_import_paths(old_file, new_file)
DialogueSettings.move_recent_file(old_file, new_file)
func _on_file_removed(file: String) -> void:
update_import_paths(file, "")
if is_instance_valid(main_view):
main_view.close_file(file)

View File

@ -0,0 +1,187 @@
@tool
extends Node
const DialogueConstants = preload("./constants.gd")
### Editor config
const DEFAULT_SETTINGS = {
states = [],
missing_translations_are_errors = false,
export_characters_in_translation = true,
wrap_lines = false,
new_with_template = true,
include_all_responses = false,
ignore_missing_state_values = false,
custom_test_scene_path = preload("./test_scene.tscn").resource_path,
default_csv_locale = "en",
balloon_path = "",
create_lines_for_responses_with_characters = true,
include_character_in_translation_exports = false,
include_notes_in_translation_exports = false,
uses_dotnet = false,
try_suppressing_startup_unsaved_indicator = false
}
static func prepare() -> void:
# Migrate previous keys
for key in [
"states",
"missing_translations_are_errors",
"export_characters_in_translation",
"wrap_lines",
"new_with_template",
"include_all_responses",
"custom_test_scene_path"
]:
if ProjectSettings.has_setting("dialogue_manager/%s" % key):
var value = ProjectSettings.get_setting("dialogue_manager/%s" % key)
ProjectSettings.set_setting("dialogue_manager/%s" % key, null)
set_setting(key, value)
# Set up initial settings
for setting in DEFAULT_SETTINGS:
var setting_name: String = "dialogue_manager/general/%s" % setting
if not ProjectSettings.has_setting(setting_name):
set_setting(setting, DEFAULT_SETTINGS[setting])
ProjectSettings.set_initial_value(setting_name, DEFAULT_SETTINGS[setting])
if setting.ends_with("_path"):
ProjectSettings.add_property_info({
"name": setting_name,
"type": TYPE_STRING,
"hint": PROPERTY_HINT_FILE,
})
# Some settings shouldn't be edited directly in the Project Settings window
ProjectSettings.set_as_internal("dialogue_manager/general/states", true)
ProjectSettings.set_as_internal("dialogue_manager/general/custom_test_scene_path", true)
ProjectSettings.set_as_internal("dialogue_manager/general/uses_dotnet", true)
ProjectSettings.save()
static func set_setting(key: String, value) -> void:
ProjectSettings.set_setting("dialogue_manager/general/%s" % key, value)
ProjectSettings.set_initial_value("dialogue_manager/general/%s" % key, DEFAULT_SETTINGS[key])
ProjectSettings.save()
static func get_setting(key: String, default):
if ProjectSettings.has_setting("dialogue_manager/general/%s" % key):
return ProjectSettings.get_setting("dialogue_manager/general/%s" % key)
else:
return default
static func get_settings(only_keys: PackedStringArray = []) -> Dictionary:
var settings: Dictionary = {}
for key in DEFAULT_SETTINGS.keys():
if only_keys.is_empty() or key in only_keys:
settings[key] = get_setting(key, DEFAULT_SETTINGS[key])
return settings
### User config
static func get_user_config() -> Dictionary:
var user_config: Dictionary = {
check_for_updates = true,
just_refreshed = null,
recent_files = [],
reopen_files = [],
most_recent_reopen_file = "",
carets = {},
run_title = "",
run_resource_path = "",
is_running_test_scene = false,
has_dotnet_solution = false,
open_in_external_editor = false
}
if FileAccess.file_exists(DialogueConstants.USER_CONFIG_PATH):
var file: FileAccess = FileAccess.open(DialogueConstants.USER_CONFIG_PATH, FileAccess.READ)
user_config.merge(JSON.parse_string(file.get_as_text()), true)
return user_config
static func save_user_config(user_config: Dictionary) -> void:
var file: FileAccess = FileAccess.open(DialogueConstants.USER_CONFIG_PATH, FileAccess.WRITE)
file.store_string(JSON.stringify(user_config))
static func set_user_value(key: String, value) -> void:
var user_config: Dictionary = get_user_config()
user_config[key] = value
save_user_config(user_config)
static func get_user_value(key: String, default = null):
return get_user_config().get(key, default)
static func add_recent_file(path: String) -> void:
var recent_files: Array = get_user_value("recent_files", [])
if path in recent_files:
recent_files.erase(path)
recent_files.insert(0, path)
set_user_value("recent_files", recent_files)
static func move_recent_file(from_path: String, to_path: String) -> void:
var recent_files: Array = get_user_value("recent_files", [])
for i in range(0, recent_files.size()):
if recent_files[i] == from_path:
recent_files[i] = to_path
set_user_value("recent_files", recent_files)
static func remove_recent_file(path: String) -> void:
var recent_files: Array = get_user_value("recent_files", [])
if path in recent_files:
recent_files.erase(path)
set_user_value("recent_files", recent_files)
static func get_recent_files() -> Array:
return get_user_value("recent_files", [])
static func clear_recent_files() -> void:
set_user_value("recent_files", [])
set_user_value("carets", {})
static func set_caret(path: String, cursor: Vector2) -> void:
var carets: Dictionary = get_user_value("carets", {})
carets[path] = {
x = cursor.x,
y = cursor.y
}
set_user_value("carets", carets)
static func get_caret(path: String) -> Vector2:
var carets = get_user_value("carets", {})
if carets.has(path):
var caret = carets.get(path)
return Vector2(caret.x, caret.y)
else:
return Vector2.ZERO
static func check_for_dotnet_solution() -> bool:
if Engine.is_editor_hint():
var has_dotnet_solution: bool = false
if ProjectSettings.has_setting("dotnet/project/solution_directory"):
var directory: String = ProjectSettings.get("dotnet/project/solution_directory")
var file_name: String = ProjectSettings.get("dotnet/project/assembly_name")
has_dotnet_solution = FileAccess.file_exists("res://%s/%s.sln" % [directory, file_name])
set_setting("uses_dotnet", has_dotnet_solution)
return has_dotnet_solution
return get_setting("uses_dotnet", false)

View File

@ -0,0 +1,32 @@
class_name BaseDialogueTestScene extends Node2D
const DialogueSettings = preload("./settings.gd")
const DialogueResource = preload("./dialogue_resource.gd")
@onready var title: String = DialogueSettings.get_user_value("run_title")
@onready var resource: DialogueResource = load(DialogueSettings.get_user_value("run_resource_path"))
func _ready():
var screen_index: int = DisplayServer.get_primary_screen()
DisplayServer.window_set_position(Vector2(DisplayServer.screen_get_position(screen_index)) + (DisplayServer.screen_get_size(screen_index) - DisplayServer.window_get_size()) * 0.5)
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
# Normally you can just call DialogueManager directly but doing so before the plugin has been
# enabled in settings will throw a compiler error here so I'm using `get_singleton` instead.
var dialogue_manager = Engine.get_singleton("DialogueManager")
dialogue_manager.dialogue_ended.connect(_on_dialogue_ended)
dialogue_manager.show_dialogue_balloon(resource, title)
func _enter_tree() -> void:
DialogueSettings.set_user_value("is_running_test_scene", false)
### Signals
func _on_dialogue_ended(_resource: DialogueResource):
get_tree().quit()

View File

@ -0,0 +1,7 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://addons/dialogue_manager/test_scene.gd" id="1_yupoh"]
[node name="TestScene" type="Node2D"]
script = ExtResource("1_yupoh")

View File

@ -0,0 +1,471 @@
extends Object
const DialogueConstants = preload("../constants.gd")
const SUPPORTED_BUILTIN_TYPES = [
TYPE_STRING,
TYPE_STRING_NAME,
TYPE_ARRAY,
TYPE_VECTOR2,
TYPE_VECTOR3,
TYPE_VECTOR4,
TYPE_DICTIONARY,
TYPE_QUATERNION,
TYPE_COLOR,
TYPE_SIGNAL,
TYPE_CALLABLE
]
static var resolve_method_error: Error = OK
static func is_supported(thing) -> bool:
return typeof(thing) in SUPPORTED_BUILTIN_TYPES
static func resolve_property(builtin, property: String):
match typeof(builtin):
TYPE_ARRAY, TYPE_DICTIONARY, TYPE_QUATERNION, TYPE_STRING, TYPE_STRING_NAME:
return builtin[property]
# Some types have constants that we need to manually resolve
TYPE_VECTOR2:
return resolve_vector2_property(builtin, property)
TYPE_VECTOR3:
return resolve_vector3_property(builtin, property)
TYPE_VECTOR4:
return resolve_vector4_property(builtin, property)
TYPE_COLOR:
return resolve_color_property(builtin, property)
static func resolve_method(thing, method_name: String, args: Array):
resolve_method_error = OK
# Resolve static methods manually
match typeof(thing):
TYPE_VECTOR2:
match method_name:
"from_angle":
return Vector2.from_angle(args[0])
TYPE_COLOR:
match method_name:
"from_hsv":
return Color.from_hsv(args[0], args[1], args[2]) if args.size() == 3 else Color.from_hsv(args[0], args[1], args[2], args[3])
"from_ok_hsl":
return Color.from_ok_hsl(args[0], args[1], args[2]) if args.size() == 3 else Color.from_ok_hsl(args[0], args[1], args[2], args[3])
"from_rgbe9995":
return Color.from_rgbe9995(args[0])
"from_string":
return Color.from_string(args[0], args[1])
TYPE_QUATERNION:
match method_name:
"from_euler":
return Quaternion.from_euler(args[0])
# Anything else can be evaulatated automatically
var references: Array = ["thing"]
for i in range(0, args.size()):
references.append("arg%d" % i)
var expression = Expression.new()
if expression.parse("thing.%s(%s)" % [method_name, ",".join(references.slice(1))], references) != OK:
assert(false, expression.get_error_text())
var result = expression.execute([thing] + args, null, false)
if expression.has_execute_failed():
resolve_method_error = ERR_CANT_RESOLVE
return null
return result
static func has_resolve_method_failed() -> bool:
return resolve_method_error != OK
static func resolve_color_property(color: Color, property: String):
match property:
"ALICE_BLUE":
return Color.ALICE_BLUE
"ANTIQUE_WHITE":
return Color.ANTIQUE_WHITE
"AQUA":
return Color.AQUA
"AQUAMARINE":
return Color.AQUAMARINE
"AZURE":
return Color.AZURE
"BEIGE":
return Color.BEIGE
"BISQUE":
return Color.BISQUE
"BLACK":
return Color.BLACK
"BLANCHED_ALMOND":
return Color.BLANCHED_ALMOND
"BLUE":
return Color.BLUE
"BLUE_VIOLET":
return Color.BLUE_VIOLET
"BROWN":
return Color.BROWN
"BURLYWOOD":
return Color.BURLYWOOD
"CADET_BLUE":
return Color.CADET_BLUE
"CHARTREUSE":
return Color.CHARTREUSE
"CHOCOLATE":
return Color.CHOCOLATE
"CORAL":
return Color.CORAL
"CORNFLOWER_BLUE":
return Color.CORNFLOWER_BLUE
"CORNSILK":
return Color.CORNSILK
"CRIMSON":
return Color.CRIMSON
"CYAN":
return Color.CYAN
"DARK_BLUE":
return Color.DARK_BLUE
"DARK_CYAN":
return Color.DARK_CYAN
"DARK_GOLDENROD":
return Color.DARK_GOLDENROD
"DARK_GRAY":
return Color.DARK_GRAY
"DARK_GREEN":
return Color.DARK_GREEN
"DARK_KHAKI":
return Color.DARK_KHAKI
"DARK_MAGENTA":
return Color.DARK_MAGENTA
"DARK_OLIVE_GREEN":
return Color.DARK_OLIVE_GREEN
"DARK_ORANGE":
return Color.DARK_ORANGE
"DARK_ORCHID":
return Color.DARK_ORCHID
"DARK_RED":
return Color.DARK_RED
"DARK_SALMON":
return Color.DARK_SALMON
"DARK_SEA_GREEN":
return Color.DARK_SEA_GREEN
"DARK_SLATE_BLUE":
return Color.DARK_SLATE_BLUE
"DARK_SLATE_GRAY":
return Color.DARK_SLATE_GRAY
"DARK_TURQUOISE":
return Color.DARK_TURQUOISE
"DARK_VIOLET":
return Color.DARK_VIOLET
"DEEP_PINK":
return Color.DEEP_PINK
"DEEP_SKY_BLUE":
return Color.DEEP_SKY_BLUE
"DIM_GRAY":
return Color.DIM_GRAY
"DODGER_BLUE":
return Color.DODGER_BLUE
"FIREBRICK":
return Color.FIREBRICK
"FLORAL_WHITE":
return Color.FLORAL_WHITE
"FOREST_GREEN":
return Color.FOREST_GREEN
"FUCHSIA":
return Color.FUCHSIA
"GAINSBORO":
return Color.GAINSBORO
"GHOST_WHITE":
return Color.GHOST_WHITE
"GOLD":
return Color.GOLD
"GOLDENROD":
return Color.GOLDENROD
"GRAY":
return Color.GRAY
"GREEN":
return Color.GREEN
"GREEN_YELLOW":
return Color.GREEN_YELLOW
"HONEYDEW":
return Color.HONEYDEW
"HOT_PINK":
return Color.HOT_PINK
"INDIAN_RED":
return Color.INDIAN_RED
"INDIGO":
return Color.INDIGO
"IVORY":
return Color.IVORY
"KHAKI":
return Color.KHAKI
"LAVENDER":
return Color.LAVENDER
"LAVENDER_BLUSH":
return Color.LAVENDER_BLUSH
"LAWN_GREEN":
return Color.LAWN_GREEN
"LEMON_CHIFFON":
return Color.LEMON_CHIFFON
"LIGHT_BLUE":
return Color.LIGHT_BLUE
"LIGHT_CORAL":
return Color.LIGHT_CORAL
"LIGHT_CYAN":
return Color.LIGHT_CYAN
"LIGHT_GOLDENROD":
return Color.LIGHT_GOLDENROD
"LIGHT_GRAY":
return Color.LIGHT_GRAY
"LIGHT_GREEN":
return Color.LIGHT_GREEN
"LIGHT_PINK":
return Color.LIGHT_PINK
"LIGHT_SALMON":
return Color.LIGHT_SALMON
"LIGHT_SEA_GREEN":
return Color.LIGHT_SEA_GREEN
"LIGHT_SKY_BLUE":
return Color.LIGHT_SKY_BLUE
"LIGHT_SLATE_GRAY":
return Color.LIGHT_SLATE_GRAY
"LIGHT_STEEL_BLUE":
return Color.LIGHT_STEEL_BLUE
"LIGHT_YELLOW":
return Color.LIGHT_YELLOW
"LIME":
return Color.LIME
"LIME_GREEN":
return Color.LIME_GREEN
"LINEN":
return Color.LINEN
"MAGENTA":
return Color.MAGENTA
"MAROON":
return Color.MAROON
"MEDIUM_AQUAMARINE":
return Color.MEDIUM_AQUAMARINE
"MEDIUM_BLUE":
return Color.MEDIUM_BLUE
"MEDIUM_ORCHID":
return Color.MEDIUM_ORCHID
"MEDIUM_PURPLE":
return Color.MEDIUM_PURPLE
"MEDIUM_SEA_GREEN":
return Color.MEDIUM_SEA_GREEN
"MEDIUM_SLATE_BLUE":
return Color.MEDIUM_SLATE_BLUE
"MEDIUM_SPRING_GREEN":
return Color.MEDIUM_SPRING_GREEN
"MEDIUM_TURQUOISE":
return Color.MEDIUM_TURQUOISE
"MEDIUM_VIOLET_RED":
return Color.MEDIUM_VIOLET_RED
"MIDNIGHT_BLUE":
return Color.MIDNIGHT_BLUE
"MINT_CREAM":
return Color.MINT_CREAM
"MISTY_ROSE":
return Color.MISTY_ROSE
"MOCCASIN":
return Color.MOCCASIN
"NAVAJO_WHITE":
return Color.NAVAJO_WHITE
"NAVY_BLUE":
return Color.NAVY_BLUE
"OLD_LACE":
return Color.OLD_LACE
"OLIVE":
return Color.OLIVE
"OLIVE_DRAB":
return Color.OLIVE_DRAB
"ORANGE":
return Color.ORANGE
"ORANGE_RED":
return Color.ORANGE_RED
"ORCHID":
return Color.ORCHID
"PALE_GOLDENROD":
return Color.PALE_GOLDENROD
"PALE_GREEN":
return Color.PALE_GREEN
"PALE_TURQUOISE":
return Color.PALE_TURQUOISE
"PALE_VIOLET_RED":
return Color.PALE_VIOLET_RED
"PAPAYA_WHIP":
return Color.PAPAYA_WHIP
"PEACH_PUFF":
return Color.PEACH_PUFF
"PERU":
return Color.PERU
"PINK":
return Color.PINK
"PLUM":
return Color.PLUM
"POWDER_BLUE":
return Color.POWDER_BLUE
"PURPLE":
return Color.PURPLE
"REBECCA_PURPLE":
return Color.REBECCA_PURPLE
"RED":
return Color.RED
"ROSY_BROWN":
return Color.ROSY_BROWN
"ROYAL_BLUE":
return Color.ROYAL_BLUE
"SADDLE_BROWN":
return Color.SADDLE_BROWN
"SALMON":
return Color.SALMON
"SANDY_BROWN":
return Color.SANDY_BROWN
"SEA_GREEN":
return Color.SEA_GREEN
"SEASHELL":
return Color.SEASHELL
"SIENNA":
return Color.SIENNA
"SILVER":
return Color.SILVER
"SKY_BLUE":
return Color.SKY_BLUE
"SLATE_BLUE":
return Color.SLATE_BLUE
"SLATE_GRAY":
return Color.SLATE_GRAY
"SNOW":
return Color.SNOW
"SPRING_GREEN":
return Color.SPRING_GREEN
"STEEL_BLUE":
return Color.STEEL_BLUE
"TAN":
return Color.TAN
"TEAL":
return Color.TEAL
"THISTLE":
return Color.THISTLE
"TOMATO":
return Color.TOMATO
"TRANSPARENT":
return Color.TRANSPARENT
"TURQUOISE":
return Color.TURQUOISE
"VIOLET":
return Color.VIOLET
"WEB_GRAY":
return Color.WEB_GRAY
"WEB_GREEN":
return Color.WEB_GREEN
"WEB_MAROON":
return Color.WEB_MAROON
"WEB_PURPLE":
return Color.WEB_PURPLE
"WHEAT":
return Color.WHEAT
"WHITE":
return Color.WHITE
"WHITE_SMOKE":
return Color.WHITE_SMOKE
"YELLOW":
return Color.YELLOW
"YELLOW_GREEN":
return Color.YELLOW_GREEN
return color[property]
static func resolve_vector2_property(vector: Vector2, property: String):
match property:
"AXIS_X":
return Vector2.AXIS_X
"AXIS_Y":
return Vector2.AXIS_Y
"ZERO":
return Vector2.ZERO
"ONE":
return Vector2.ONE
"INF":
return Vector2.INF
"LEFT":
return Vector2.LEFT
"RIGHT":
return Vector2.RIGHT
"UP":
return Vector2.UP
"DOWN":
return Vector2.DOWN
return vector[property]
static func resolve_vector3_property(vector: Vector3, property: String):
match property:
"AXIS_X":
return Vector3.AXIS_X
"AXIS_Y":
return Vector3.AXIS_Y
"AXIS_Z":
return Vector3.AXIS_Z
"ZERO":
return Vector3.ZERO
"ONE":
return Vector3.ONE
"INF":
return Vector3.INF
"LEFT":
return Vector3.LEFT
"RIGHT":
return Vector3.RIGHT
"UP":
return Vector3.UP
"DOWN":
return Vector3.DOWN
"FORWARD":
return Vector3.FORWARD
"BACK":
return Vector3.BACK
"MODEL_LEFT":
return Vector3(1, 0, 0)
"MODEL_RIGHT":
return Vector3(-1, 0, 0)
"MODEL_TOP":
return Vector3(0, 1, 0)
"MODEL_BOTTOM":
return Vector3(0, -1, 0)
"MODEL_FRONT":
return Vector3(0, 0, 1)
"MODEL_REAR":
return Vector3(0, 0, -1)
return vector[property]
static func resolve_vector4_property(vector: Vector4, property: String):
match property:
"AXIS_X":
return Vector4.AXIS_X
"AXIS_Y":
return Vector4.AXIS_Y
"AXIS_Z":
return Vector4.AXIS_Z
"AXIS_W":
return Vector4.AXIS_W
"ZERO":
return Vector4.ZERO
"ONE":
return Vector4.ONE
"INF":
return Vector4.INF
return vector[property]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,431 @@
[gd_scene load_steps=16 format=3 uid="uid://cbuf1q3xsse3q"]
[ext_resource type="Script" path="res://addons/dialogue_manager/views/main_view.gd" id="1_h6qfq"]
[ext_resource type="PackedScene" uid="uid://civ6shmka5e8u" path="res://addons/dialogue_manager/components/code_edit.tscn" id="2_f73fm"]
[ext_resource type="PackedScene" uid="uid://dnufpcdrreva3" path="res://addons/dialogue_manager/components/files_list.tscn" id="2_npj2k"]
[ext_resource type="PackedScene" uid="uid://ctns6ouwwd68i" path="res://addons/dialogue_manager/components/title_list.tscn" id="2_onb4i"]
[ext_resource type="PackedScene" uid="uid://co8yl23idiwbi" path="res://addons/dialogue_manager/components/update_button.tscn" id="2_ph3vs"]
[ext_resource type="PackedScene" uid="uid://gr8nakpbrhby" path="res://addons/dialogue_manager/components/search_and_replace.tscn" id="6_ylh0t"]
[ext_resource type="PackedScene" uid="uid://cs8pwrxr5vxix" path="res://addons/dialogue_manager/components/errors_panel.tscn" id="7_5cvl4"]
[ext_resource type="Script" path="res://addons/dialogue_manager/components/code_edit_syntax_highlighter.gd" id="7_necsa"]
[ext_resource type="PackedScene" uid="uid://cpg4lg1r3ff6m" path="res://addons/dialogue_manager/views/settings_view.tscn" id="9_8bf36"]
[ext_resource type="PackedScene" uid="uid://0n7hwviyyly4" path="res://addons/dialogue_manager/components/find_in_files.tscn" id="10_yold3"]
[sub_resource type="Image" id="Image_w5tip"]
data = {
"data": PackedByteArray(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, 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, 94, 94, 127, 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, 94, 94, 127, 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, 94, 94, 127, 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, 255, 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, 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, 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, 94, 94, 54, 255, 94, 94, 57, 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, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 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, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 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, 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, 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",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="ImageTexture_wmrmd"]
image = SubResource("Image_w5tip")
[sub_resource type="Image" id="Image_ki84n"]
data = {
"data": PackedByteArray(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, 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, 94, 94, 127, 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, 94, 94, 127, 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, 94, 94, 127, 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, 255, 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, 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, 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, 94, 94, 54, 255, 94, 94, 57, 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, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 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, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 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, 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, 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",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="ImageTexture_r0npg"]
image = SubResource("Image_ki84n")
[sub_resource type="SyntaxHighlighter" id="SyntaxHighlighter_4re8k"]
script = ExtResource("7_necsa")
[node name="MainView" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 3
script = ExtResource("1_h6qfq")
[node name="ParseTimer" type="Timer" parent="."]
[node name="Margin" type="MarginContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_vertical = 3
theme_override_constants/margin_left = 5
theme_override_constants/margin_right = 5
theme_override_constants/margin_bottom = 5
metadata/_edit_layout_mode = 1
[node name="Content" type="HSplitContainer" parent="Margin"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
[node name="SidePanel" type="VBoxContainer" parent="Margin/Content"]
custom_minimum_size = Vector2(150, 0)
layout_mode = 2
size_flags_horizontal = 3
[node name="Toolbar" type="HBoxContainer" parent="Margin/Content/SidePanel"]
layout_mode = 2
[node name="NewButton" type="Button" parent="Margin/Content/SidePanel/Toolbar"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Start a new file"
flat = true
[node name="OpenButton" type="MenuButton" parent="Margin/Content/SidePanel/Toolbar"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Open a file"
item_count = 9
popup/item_0/text = "Open..."
popup/item_0/icon = SubResource("ImageTexture_wmrmd")
popup/item_0/id = 100
popup/item_1/text = ""
popup/item_1/id = -1
popup/item_1/separator = true
popup/item_2/text = "res://blah.dialogue"
popup/item_2/icon = SubResource("ImageTexture_wmrmd")
popup/item_2/id = 2
popup/item_3/text = "res://examples/dialogue.dialogue"
popup/item_3/icon = SubResource("ImageTexture_wmrmd")
popup/item_3/id = 3
popup/item_4/text = "res://examples/dialogue_with_input.dialogue"
popup/item_4/icon = SubResource("ImageTexture_wmrmd")
popup/item_4/id = 4
popup/item_5/text = "res://examples/dialogue_for_point_n_click.dialogue"
popup/item_5/icon = SubResource("ImageTexture_wmrmd")
popup/item_5/id = 5
popup/item_6/text = "res://examples/dialogue_for_visual_novel.dialogue"
popup/item_6/icon = SubResource("ImageTexture_wmrmd")
popup/item_6/id = 6
popup/item_7/text = ""
popup/item_7/id = -1
popup/item_7/separator = true
popup/item_8/text = "Clear recent files"
popup/item_8/id = 101
[node name="SaveAllButton" type="Button" parent="Margin/Content/SidePanel/Toolbar"]
unique_name_in_owner = true
layout_mode = 2
disabled = true
flat = true
[node name="FindInFilesButton" type="Button" parent="Margin/Content/SidePanel/Toolbar"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Find in files..."
flat = true
[node name="Bookmarks" type="VSplitContainer" parent="Margin/Content/SidePanel"]
layout_mode = 2
size_flags_vertical = 3
[node name="FilesList" parent="Margin/Content/SidePanel/Bookmarks" instance=ExtResource("2_npj2k")]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
[node name="FilesPopupMenu" type="PopupMenu" parent="Margin/Content/SidePanel/Bookmarks/FilesList"]
unique_name_in_owner = true
[node name="TitleList" parent="Margin/Content/SidePanel/Bookmarks" instance=ExtResource("2_onb4i")]
unique_name_in_owner = true
layout_mode = 2
[node name="CodePanel" type="VBoxContainer" parent="Margin/Content"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 4.0
[node name="Toolbar" type="HBoxContainer" parent="Margin/Content/CodePanel"]
layout_mode = 2
[node name="InsertButton" type="MenuButton" parent="Margin/Content/CodePanel/Toolbar"]
unique_name_in_owner = true
layout_mode = 2
text = "Insert"
item_count = 15
popup/item_0/text = "Wave BBCode"
popup/item_0/icon = SubResource("ImageTexture_r0npg")
popup/item_0/id = 0
popup/item_1/text = "Shake BBCode"
popup/item_1/icon = SubResource("ImageTexture_r0npg")
popup/item_1/id = 1
popup/item_2/text = ""
popup/item_2/id = -1
popup/item_2/separator = true
popup/item_3/text = "Typing pause"
popup/item_3/icon = SubResource("ImageTexture_r0npg")
popup/item_3/id = 3
popup/item_4/text = "Typing speed change"
popup/item_4/icon = SubResource("ImageTexture_r0npg")
popup/item_4/id = 4
popup/item_5/text = "Auto advance"
popup/item_5/icon = SubResource("ImageTexture_r0npg")
popup/item_5/id = 5
popup/item_6/text = "Templates"
popup/item_6/id = -1
popup/item_6/separator = true
popup/item_7/text = "Title"
popup/item_7/icon = SubResource("ImageTexture_r0npg")
popup/item_7/id = 6
popup/item_8/text = "Dialogue"
popup/item_8/icon = SubResource("ImageTexture_r0npg")
popup/item_8/id = 7
popup/item_9/text = "Response"
popup/item_9/icon = SubResource("ImageTexture_r0npg")
popup/item_9/id = 8
popup/item_10/text = "Random lines"
popup/item_10/icon = SubResource("ImageTexture_r0npg")
popup/item_10/id = 9
popup/item_11/text = "Random text"
popup/item_11/icon = SubResource("ImageTexture_r0npg")
popup/item_11/id = 10
popup/item_12/text = "Actions"
popup/item_12/id = -1
popup/item_12/separator = true
popup/item_13/text = "Jump to title"
popup/item_13/icon = SubResource("ImageTexture_r0npg")
popup/item_13/id = 11
popup/item_14/text = "End dialogue"
popup/item_14/icon = SubResource("ImageTexture_r0npg")
popup/item_14/id = 12
[node name="TranslationsButton" type="MenuButton" parent="Margin/Content/CodePanel/Toolbar"]
unique_name_in_owner = true
layout_mode = 2
text = "Translations"
item_count = 5
popup/item_0/text = "Generate line IDs"
popup/item_0/icon = SubResource("ImageTexture_r0npg")
popup/item_0/id = 100
popup/item_1/text = ""
popup/item_1/id = -1
popup/item_1/separator = true
popup/item_2/text = "Save character names to CSV..."
popup/item_2/icon = SubResource("ImageTexture_r0npg")
popup/item_2/id = 201
popup/item_3/text = "Save lines to CSV..."
popup/item_3/icon = SubResource("ImageTexture_r0npg")
popup/item_3/id = 202
popup/item_4/text = "Import line changes from CSV..."
popup/item_4/icon = SubResource("ImageTexture_r0npg")
popup/item_4/id = 203
[node name="Separator" type="VSeparator" parent="Margin/Content/CodePanel/Toolbar"]
layout_mode = 2
[node name="SearchButton" type="Button" parent="Margin/Content/CodePanel/Toolbar"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Search for text"
toggle_mode = true
flat = true
[node name="TestButton" type="Button" parent="Margin/Content/CodePanel/Toolbar"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Test dialogue"
flat = true
[node name="Separator3" type="VSeparator" parent="Margin/Content/CodePanel/Toolbar"]
layout_mode = 2
[node name="SettingsButton" type="Button" parent="Margin/Content/CodePanel/Toolbar"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Settings"
flat = true
[node name="Spacer2" type="Control" parent="Margin/Content/CodePanel/Toolbar"]
layout_mode = 2
size_flags_horizontal = 3
[node name="SupportButton" type="Button" parent="Margin/Content/CodePanel/Toolbar"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Support Dialogue Manager"
text = "Sponsor"
flat = true
[node name="Separator4" type="VSeparator" parent="Margin/Content/CodePanel/Toolbar"]
layout_mode = 2
[node name="DocsButton" type="Button" parent="Margin/Content/CodePanel/Toolbar"]
unique_name_in_owner = true
layout_mode = 2
text = "Docs"
flat = true
[node name="VersionLabel" type="Label" parent="Margin/Content/CodePanel/Toolbar"]
unique_name_in_owner = true
modulate = Color(1, 1, 1, 0.490196)
layout_mode = 2
text = "v2.41.3"
vertical_alignment = 1
[node name="UpdateButton" parent="Margin/Content/CodePanel/Toolbar" instance=ExtResource("2_ph3vs")]
unique_name_in_owner = true
layout_mode = 2
[node name="SearchAndReplace" parent="Margin/Content/CodePanel" instance=ExtResource("6_ylh0t")]
unique_name_in_owner = true
layout_mode = 2
[node name="CodeEdit" parent="Margin/Content/CodePanel" instance=ExtResource("2_f73fm")]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_colors/background_color = Color(0.156863, 0.164706, 0.211765, 1)
theme_override_colors/font_color = Color(0.972549, 0.972549, 0.94902, 1)
theme_override_colors/bookmark_color = Color(1, 0.333333, 0.333333, 1)
theme_override_colors/current_line_color = Color(0.266667, 0.278431, 0.352941, 0.243137)
theme_override_font_sizes/font_size = 21
text = "~ this_is_a_node_title
Nathan: [[Hi|Hello|Howdy]], this is some dialogue.
Nathan: Here are some choices.
- First one
Nathan: You picked the first one.
- Second one
Nathan: You picked the second one.
- Start again => this_is_a_node_title
- End the conversation => END
Nathan: For more information see the online documentation.
=> END"
scroll_smooth = true
syntax_highlighter = SubResource("SyntaxHighlighter_4re8k")
[node name="ErrorsPanel" parent="Margin/Content/CodePanel" instance=ExtResource("7_5cvl4")]
unique_name_in_owner = true
layout_mode = 2
[node name="NewDialog" type="FileDialog" parent="."]
size = Vector2i(900, 750)
min_size = Vector2i(900, 750)
dialog_hide_on_ok = true
filters = PackedStringArray("*.dialogue ; Dialogue")
[node name="SaveDialog" type="FileDialog" parent="."]
size = Vector2i(900, 750)
min_size = Vector2i(900, 750)
dialog_hide_on_ok = true
filters = PackedStringArray("*.dialogue ; Dialogue")
[node name="OpenDialog" type="FileDialog" parent="."]
title = "Open a File"
size = Vector2i(900, 750)
min_size = Vector2i(900, 750)
ok_button_text = "Open"
dialog_hide_on_ok = true
file_mode = 0
filters = PackedStringArray("*.dialogue ; Dialogue")
[node name="ExportDialog" type="FileDialog" parent="."]
size = Vector2i(900, 750)
min_size = Vector2i(900, 750)
[node name="ImportDialog" type="FileDialog" parent="."]
title = "Open a File"
size = Vector2i(900, 750)
min_size = Vector2i(900, 750)
ok_button_text = "Open"
file_mode = 0
filters = PackedStringArray("*.csv ; Translation CSV")
[node name="ErrorsDialog" type="AcceptDialog" parent="."]
title = "Error"
dialog_text = "You have errors in your script. Fix them and then try again."
[node name="SettingsDialog" type="AcceptDialog" parent="."]
title = "Settings"
size = Vector2i(1500, 900)
unresizable = true
min_size = Vector2i(1500, 900)
max_size = Vector2i(1500, 900)
ok_button_text = "Done"
[node name="SettingsView" parent="SettingsDialog" instance=ExtResource("9_8bf36")]
offset_left = 8.0
offset_top = 8.0
offset_right = -8.0
offset_bottom = -49.0
current_tab = 0
[node name="BuildErrorDialog" type="AcceptDialog" parent="."]
title = "Errors"
dialog_text = "You need to fix dialogue errors before you can run your game."
[node name="CloseConfirmationDialog" type="ConfirmationDialog" parent="."]
title = "Unsaved changes"
ok_button_text = "Save changes"
[node name="UpdatedDialog" type="AcceptDialog" parent="."]
title = "Updated"
size = Vector2i(191, 100)
dialog_text = "You're now up to date!"
[node name="FindInFilesDialog" type="AcceptDialog" parent="."]
title = "Find in files"
size = Vector2i(1200, 900)
min_size = Vector2i(1200, 900)
ok_button_text = "Done"
[node name="FindInFiles" parent="FindInFilesDialog" node_paths=PackedStringArray("main_view", "code_edit") instance=ExtResource("10_yold3")]
custom_minimum_size = Vector2(400, 400)
offset_left = 8.0
offset_top = 8.0
offset_right = -8.0
offset_bottom = -49.0
main_view = NodePath("../..")
code_edit = NodePath("../../Margin/Content/CodePanel/CodeEdit")
[connection signal="theme_changed" from="." to="." method="_on_main_view_theme_changed"]
[connection signal="visibility_changed" from="." to="." method="_on_main_view_visibility_changed"]
[connection signal="timeout" from="ParseTimer" to="." method="_on_parse_timer_timeout"]
[connection signal="pressed" from="Margin/Content/SidePanel/Toolbar/NewButton" to="." method="_on_new_button_pressed"]
[connection signal="about_to_popup" from="Margin/Content/SidePanel/Toolbar/OpenButton" to="." method="_on_open_button_about_to_popup"]
[connection signal="pressed" from="Margin/Content/SidePanel/Toolbar/SaveAllButton" to="." method="_on_save_all_button_pressed"]
[connection signal="pressed" from="Margin/Content/SidePanel/Toolbar/FindInFilesButton" to="." method="_on_find_in_files_button_pressed"]
[connection signal="file_middle_clicked" from="Margin/Content/SidePanel/Bookmarks/FilesList" to="." method="_on_files_list_file_middle_clicked"]
[connection signal="file_popup_menu_requested" from="Margin/Content/SidePanel/Bookmarks/FilesList" to="." method="_on_files_list_file_popup_menu_requested"]
[connection signal="file_selected" from="Margin/Content/SidePanel/Bookmarks/FilesList" to="." method="_on_files_list_file_selected"]
[connection signal="about_to_popup" from="Margin/Content/SidePanel/Bookmarks/FilesList/FilesPopupMenu" to="." method="_on_files_popup_menu_about_to_popup"]
[connection signal="id_pressed" from="Margin/Content/SidePanel/Bookmarks/FilesList/FilesPopupMenu" to="." method="_on_files_popup_menu_id_pressed"]
[connection signal="title_selected" from="Margin/Content/SidePanel/Bookmarks/TitleList" to="." method="_on_title_list_title_selected"]
[connection signal="toggled" from="Margin/Content/CodePanel/Toolbar/SearchButton" to="." method="_on_search_button_toggled"]
[connection signal="pressed" from="Margin/Content/CodePanel/Toolbar/TestButton" to="." method="_on_test_button_pressed"]
[connection signal="pressed" from="Margin/Content/CodePanel/Toolbar/SettingsButton" to="." method="_on_settings_button_pressed"]
[connection signal="pressed" from="Margin/Content/CodePanel/Toolbar/SupportButton" to="." method="_on_support_button_pressed"]
[connection signal="pressed" from="Margin/Content/CodePanel/Toolbar/DocsButton" to="." method="_on_docs_button_pressed"]
[connection signal="close_requested" from="Margin/Content/CodePanel/SearchAndReplace" to="." method="_on_search_and_replace_close_requested"]
[connection signal="open_requested" from="Margin/Content/CodePanel/SearchAndReplace" to="." method="_on_search_and_replace_open_requested"]
[connection signal="active_title_change" from="Margin/Content/CodePanel/CodeEdit" to="." method="_on_code_edit_active_title_change"]
[connection signal="caret_changed" from="Margin/Content/CodePanel/CodeEdit" to="." method="_on_code_edit_caret_changed"]
[connection signal="error_clicked" from="Margin/Content/CodePanel/CodeEdit" to="." method="_on_code_edit_error_clicked"]
[connection signal="external_file_requested" from="Margin/Content/CodePanel/CodeEdit" to="." method="_on_code_edit_external_file_requested"]
[connection signal="text_changed" from="Margin/Content/CodePanel/CodeEdit" to="." method="_on_code_edit_text_changed"]
[connection signal="error_pressed" from="Margin/Content/CodePanel/ErrorsPanel" to="." method="_on_errors_panel_error_pressed"]
[connection signal="file_selected" from="NewDialog" to="." method="_on_new_dialog_file_selected"]
[connection signal="file_selected" from="SaveDialog" to="." method="_on_save_dialog_file_selected"]
[connection signal="file_selected" from="OpenDialog" to="." method="_on_open_dialog_file_selected"]
[connection signal="file_selected" from="ExportDialog" to="." method="_on_export_dialog_file_selected"]
[connection signal="file_selected" from="ImportDialog" to="." method="_on_import_dialog_file_selected"]
[connection signal="confirmed" from="SettingsDialog" to="." method="_on_settings_dialog_confirmed"]
[connection signal="script_button_pressed" from="SettingsDialog/SettingsView" to="." method="_on_settings_view_script_button_pressed"]
[connection signal="confirmed" from="CloseConfirmationDialog" to="." method="_on_close_confirmation_dialog_confirmed"]
[connection signal="custom_action" from="CloseConfirmationDialog" to="." method="_on_close_confirmation_dialog_custom_action"]
[connection signal="result_selected" from="FindInFilesDialog/FindInFiles" to="." method="_on_find_in_files_result_selected"]

View File

@ -0,0 +1,280 @@
@tool
extends TabContainer
signal script_button_pressed(path: String)
const DialogueConstants = preload("../constants.gd")
const DialogueSettings = preload("../settings.gd")
const BaseDialogueTestScene = preload("../test_scene.gd")
enum PathTarget {
CustomTestScene,
Balloon
}
# Editor
@onready var new_template_button: CheckBox = $Editor/NewTemplateButton
@onready var characters_translations_button: CheckBox = $Editor/CharactersTranslationsButton
@onready var wrap_lines_button: Button = $Editor/WrapLinesButton
@onready var default_csv_locale: LineEdit = $Editor/DefaultCSVLocale
# Runtime
@onready var include_all_responses_button: CheckBox = $Runtime/IncludeAllResponsesButton
@onready var ignore_missing_state_values: CheckBox = $Runtime/IgnoreMissingStateValues
@onready var balloon_path_input: LineEdit = $Runtime/CustomBalloon/BalloonPath
@onready var revert_balloon_button: Button = $Runtime/CustomBalloon/RevertBalloonPath
@onready var load_balloon_button: Button = $Runtime/CustomBalloon/LoadBalloonPath
@onready var states_title: Label = $Runtime/StatesTitle
@onready var globals_list: Tree = $Runtime/GlobalsList
# Advanced
@onready var check_for_updates: CheckBox = $Advanced/CheckForUpdates
@onready var include_characters_in_translations: CheckBox = $Advanced/IncludeCharactersInTranslations
@onready var include_notes_in_translations: CheckBox = $Advanced/IncludeNotesInTranslations
@onready var open_in_external_editor_button: CheckBox = $Advanced/OpenInExternalEditorButton
@onready var test_scene_path_input: LineEdit = $Advanced/CustomTestScene/TestScenePath
@onready var revert_test_scene_button: Button = $Advanced/CustomTestScene/RevertTestScene
@onready var load_test_scene_button: Button = $Advanced/CustomTestScene/LoadTestScene
@onready var custom_test_scene_file_dialog: FileDialog = $CustomTestSceneFileDialog
@onready var create_lines_for_response_characters: CheckBox = $Advanced/CreateLinesForResponseCharacters
@onready var missing_translations_button: CheckBox = $Advanced/MissingTranslationsButton
var all_globals: Dictionary = {}
var enabled_globals: Array = []
var path_target: PathTarget = PathTarget.CustomTestScene
var _default_test_scene_path: String = preload("../test_scene.tscn").resource_path
var _recompile_if_changed_settings: Dictionary
func _ready() -> void:
new_template_button.text = DialogueConstants.translate(&"settings.new_template")
$Editor/MissingTranslationsHint.text = DialogueConstants.translate(&"settings.missing_keys_hint")
characters_translations_button.text = DialogueConstants.translate(&"settings.characters_translations")
wrap_lines_button.text = DialogueConstants.translate(&"settings.wrap_long_lines")
$Editor/DefaultCSVLocaleLabel.text = DialogueConstants.translate(&"settings.default_csv_locale")
include_all_responses_button.text = DialogueConstants.translate(&"settings.include_failed_responses")
ignore_missing_state_values.text = DialogueConstants.translate(&"settings.ignore_missing_state_values")
$Runtime/CustomBalloonLabel.text = DialogueConstants.translate(&"settings.default_balloon_hint")
states_title.text = DialogueConstants.translate(&"settings.states_shortcuts")
$Runtime/StatesMessage.text = DialogueConstants.translate(&"settings.states_message")
$Runtime/StatesHint.text = DialogueConstants.translate(&"settings.states_hint")
check_for_updates.text = DialogueConstants.translate(&"settings.check_for_updates")
include_characters_in_translations.text = DialogueConstants.translate(&"settings.include_characters_in_translations")
include_notes_in_translations.text = DialogueConstants.translate(&"settings.include_notes_in_translations")
open_in_external_editor_button.text = DialogueConstants.translate(&"settings.open_in_external_editor")
$Advanced/ExternalWarning.text = DialogueConstants.translate(&"settings.external_editor_warning")
$Advanced/CustomTestSceneLabel.text = DialogueConstants.translate(&"settings.custom_test_scene")
$Advanced/RecompileWarning.text = DialogueConstants.translate(&"settings.recompile_warning")
missing_translations_button.text = DialogueConstants.translate(&"settings.missing_keys")
create_lines_for_response_characters.text = DialogueConstants.translate(&"settings.create_lines_for_responses_with_characters")
current_tab = 0
func prepare() -> void:
_recompile_if_changed_settings = _get_settings_that_require_recompilation()
test_scene_path_input.placeholder_text = DialogueSettings.get_setting("custom_test_scene_path", _default_test_scene_path)
revert_test_scene_button.visible = test_scene_path_input.placeholder_text != _default_test_scene_path
revert_test_scene_button.icon = get_theme_icon("RotateLeft", "EditorIcons")
revert_test_scene_button.tooltip_text = DialogueConstants.translate(&"settings.revert_to_default_test_scene")
load_test_scene_button.icon = get_theme_icon("Load", "EditorIcons")
var balloon_path: String = DialogueSettings.get_setting("balloon_path", "")
if not FileAccess.file_exists(balloon_path):
DialogueSettings.set_setting("balloon_path", "")
balloon_path = ""
balloon_path_input.placeholder_text = balloon_path if balloon_path != "" else DialogueConstants.translate(&"settings.default_balloon_path")
revert_balloon_button.visible = balloon_path != ""
revert_balloon_button.icon = get_theme_icon("RotateLeft", "EditorIcons")
revert_balloon_button.tooltip_text = DialogueConstants.translate(&"settings.revert_to_default_balloon")
load_balloon_button.icon = get_theme_icon("Load", "EditorIcons")
var scale: float = Engine.get_meta("DialogueManagerPlugin").get_editor_interface().get_editor_scale()
custom_test_scene_file_dialog.min_size = Vector2(600, 500) * scale
states_title.add_theme_font_override("font", get_theme_font("bold", "EditorFonts"))
check_for_updates.set_pressed_no_signal(DialogueSettings.get_user_value("check_for_updates", true))
characters_translations_button.set_pressed_no_signal(DialogueSettings.get_setting("export_characters_in_translation", true))
wrap_lines_button.set_pressed_no_signal(DialogueSettings.get_setting("wrap_lines", false))
include_all_responses_button.set_pressed_no_signal(DialogueSettings.get_setting("include_all_responses", false))
ignore_missing_state_values.set_pressed_no_signal(DialogueSettings.get_setting("ignore_missing_state_values", false))
new_template_button.set_pressed_no_signal(DialogueSettings.get_setting("new_with_template", true))
default_csv_locale.text = DialogueSettings.get_setting("default_csv_locale", "en")
missing_translations_button.set_pressed_no_signal(DialogueSettings.get_setting("missing_translations_are_errors", false))
create_lines_for_response_characters.set_pressed_no_signal(DialogueSettings.get_setting("create_lines_for_responses_with_characters", true))
include_characters_in_translations.set_pressed_no_signal(DialogueSettings.get_setting("include_character_in_translation_exports", false))
include_notes_in_translations.set_pressed_no_signal(DialogueSettings.get_setting("include_notes_in_translation_exports", false))
open_in_external_editor_button.set_pressed_no_signal(DialogueSettings.get_user_value("open_in_external_editor", false))
var editor_settings: EditorSettings = Engine.get_meta("DialogueManagerPlugin").get_editor_interface().get_editor_settings()
var external_editor: String = editor_settings.get_setting("text_editor/external/exec_path")
var use_external_editor: bool = editor_settings.get_setting("text_editor/external/use_external_editor") and external_editor != ""
if not use_external_editor:
open_in_external_editor_button.hide()
$Advanced/ExternalWarning.hide()
$Advanced/ExternalSeparator.hide()
var project = ConfigFile.new()
var err = project.load("res://project.godot")
assert(err == OK, "Could not find the project file")
all_globals.clear()
if project.has_section("autoload"):
for key in project.get_section_keys("autoload"):
if key != "DialogueManager":
all_globals[key] = project.get_value("autoload", key)
enabled_globals = DialogueSettings.get_setting("states", []).duplicate()
globals_list.clear()
var root = globals_list.create_item()
for name in all_globals.keys():
var item: TreeItem = globals_list.create_item(root)
item.set_cell_mode(0, TreeItem.CELL_MODE_CHECK)
item.set_checked(0, name in enabled_globals)
item.set_text(0, name)
item.add_button(1, get_theme_icon("Edit", "EditorIcons"))
item.set_text(2, all_globals.get(name, "").replace("*res://", "res://"))
globals_list.set_column_expand(0, false)
globals_list.set_column_custom_minimum_width(0, 250)
globals_list.set_column_expand(1, false)
globals_list.set_column_custom_minimum_width(1, 40)
globals_list.set_column_titles_visible(true)
globals_list.set_column_title(0, DialogueConstants.translate(&"settings.autoload"))
globals_list.set_column_title(1, "")
globals_list.set_column_title(2, DialogueConstants.translate(&"settings.path"))
func apply_settings_changes() -> void:
if _recompile_if_changed_settings != _get_settings_that_require_recompilation():
Engine.get_meta("DialogueCache").reimport_files()
func _get_settings_that_require_recompilation() -> Dictionary:
return DialogueSettings.get_settings([
"missing_translations_are_errors",
"create_lines_for_responses_with_characters"
])
### Signals
func _on_missing_translations_button_toggled(toggled_on: bool) -> void:
DialogueSettings.set_setting("missing_translations_are_errors", toggled_on)
func _on_characters_translations_button_toggled(toggled_on: bool) -> void:
DialogueSettings.set_setting("export_characters_in_translation", toggled_on)
func _on_wrap_lines_button_toggled(toggled_on: bool) -> void:
DialogueSettings.set_setting("wrap_lines", toggled_on)
func _on_include_all_responses_button_toggled(toggled_on: bool) -> void:
DialogueSettings.set_setting("include_all_responses", toggled_on)
func _on_globals_list_item_selected() -> void:
var item = globals_list.get_selected()
var is_checked = not item.is_checked(0)
item.set_checked(0, is_checked)
if is_checked:
enabled_globals.append(item.get_text(0))
else:
enabled_globals.erase(item.get_text(0))
DialogueSettings.set_setting("states", enabled_globals)
func _on_globals_list_button_clicked(item: TreeItem, column: int, id: int, mouse_button_index: int) -> void:
emit_signal("script_button_pressed", item.get_text(2))
func _on_sample_template_toggled(toggled_on):
DialogueSettings.set_setting("new_with_template", toggled_on)
func _on_revert_test_scene_pressed() -> void:
DialogueSettings.set_setting("custom_test_scene_path", _default_test_scene_path)
test_scene_path_input.placeholder_text = _default_test_scene_path
revert_test_scene_button.visible = test_scene_path_input.placeholder_text != _default_test_scene_path
func _on_load_test_scene_pressed() -> void:
path_target = PathTarget.CustomTestScene
custom_test_scene_file_dialog.popup_centered()
func _on_custom_test_scene_file_dialog_file_selected(path: String) -> void:
match path_target:
PathTarget.CustomTestScene:
# Check that the test scene is a subclass of BaseDialogueTestScene
var test_scene: PackedScene = load(path)
if test_scene and test_scene.instantiate() is BaseDialogueTestScene:
DialogueSettings.set_setting("custom_test_scene_path", path)
test_scene_path_input.placeholder_text = path
revert_test_scene_button.visible = test_scene_path_input.placeholder_text != _default_test_scene_path
else:
var accept: AcceptDialog = AcceptDialog.new()
accept.dialog_text = DialogueConstants.translate(&"settings.invalid_test_scene").format({ path = path })
add_child(accept)
accept.popup_centered.call_deferred()
PathTarget.Balloon:
DialogueSettings.set_setting("balloon_path", path)
balloon_path_input.placeholder_text = path
revert_balloon_button.visible = balloon_path_input.placeholder_text != ""
func _on_ignore_missing_state_values_toggled(toggled_on: bool) -> void:
DialogueSettings.set_setting("ignore_missing_state_values", toggled_on)
func _on_default_csv_locale_text_changed(new_text: String) -> void:
DialogueSettings.set_setting("default_csv_locale", new_text)
func _on_revert_balloon_path_pressed() -> void:
DialogueSettings.set_setting("balloon_path", "")
balloon_path_input.placeholder_text = DialogueConstants.translate(&"settings.default_balloon_path")
revert_balloon_button.visible = DialogueSettings.get_setting("balloon_path", "") != ""
func _on_load_balloon_path_pressed() -> void:
path_target = PathTarget.Balloon
custom_test_scene_file_dialog.popup_centered()
func _on_create_lines_for_response_characters_toggled(toggled_on: bool) -> void:
DialogueSettings.set_setting("create_lines_for_responses_with_characters", toggled_on)
func _on_open_in_external_editor_button_toggled(toggled_on: bool) -> void:
DialogueSettings.set_user_value("open_in_external_editor", toggled_on)
func _on_include_characters_in_translations_toggled(toggled_on: bool) -> void:
DialogueSettings.set_setting("include_character_in_translation_exports", toggled_on)
func _on_include_notes_in_translations_toggled(toggled_on: bool) -> void:
DialogueSettings.set_setting("include_notes_in_translation_exports", toggled_on)
func _on_keep_up_to_date_toggled(toggled_on: bool) -> void:
DialogueSettings.set_user_value("check_for_updates", toggled_on)

View File

@ -0,0 +1,221 @@
[gd_scene load_steps=3 format=3 uid="uid://cpg4lg1r3ff6m"]
[ext_resource type="Script" path="res://addons/dialogue_manager/views/settings_view.gd" id="1_06uxa"]
[sub_resource type="Theme" id="Theme_3a8rc"]
HSeparator/constants/separation = 20
[node name="SettingsView" type="TabContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_right = -206.0
offset_bottom = -345.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 3
theme = SubResource("Theme_3a8rc")
current_tab = 2
script = ExtResource("1_06uxa")
[node name="Editor" type="VBoxContainer" parent="."]
visible = false
layout_mode = 2
[node name="NewTemplateButton" type="CheckBox" parent="Editor"]
layout_mode = 2
button_pressed = true
text = "New dialogue files will start with template text"
[node name="MissingTranslationsHint" type="Label" parent="Editor"]
modulate = Color(1, 1, 1, 0.501961)
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
text = "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."
autowrap_mode = 3
[node name="CharactersTranslationsButton" type="CheckBox" parent="Editor"]
layout_mode = 2
button_pressed = true
text = "Export character names in translation files"
[node name="WrapLinesButton" type="CheckBox" parent="Editor"]
layout_mode = 2
button_pressed = true
text = "Wrap long lines"
[node name="HSeparator" type="HSeparator" parent="Editor"]
layout_mode = 2
[node name="DefaultCSVLocaleLabel" type="Label" parent="Editor"]
layout_mode = 2
text = "Default CSV Locale"
[node name="DefaultCSVLocale" type="LineEdit" parent="Editor"]
layout_mode = 2
[node name="Runtime" type="VBoxContainer" parent="."]
visible = false
layout_mode = 2
[node name="IncludeAllResponsesButton" type="CheckBox" parent="Runtime"]
layout_mode = 2
text = "Include responses with failed conditions"
[node name="IgnoreMissingStateValues" type="CheckBox" parent="Runtime"]
layout_mode = 2
text = "Skip over missing state value errors (not recommended)"
[node name="HSeparator" type="HSeparator" parent="Runtime"]
layout_mode = 2
[node name="CustomBalloonLabel" type="Label" parent="Runtime"]
layout_mode = 2
text = "Custom balloon to use when calling \"DialogueManager.show_balloon()\""
[node name="CustomBalloon" type="HBoxContainer" parent="Runtime"]
layout_mode = 2
[node name="BalloonPath" type="LineEdit" parent="Runtime/CustomBalloon"]
layout_mode = 2
size_flags_horizontal = 3
focus_mode = 0
editable = false
shortcut_keys_enabled = false
middle_mouse_paste_enabled = false
[node name="RevertBalloonPath" type="Button" parent="Runtime/CustomBalloon"]
visible = false
layout_mode = 2
tooltip_text = "Revert to default test scene"
flat = true
[node name="LoadBalloonPath" type="Button" parent="Runtime/CustomBalloon"]
layout_mode = 2
[node name="HSeparator2" type="HSeparator" parent="Runtime"]
layout_mode = 2
[node name="StatesTitle" type="Label" parent="Runtime"]
layout_mode = 2
text = "State Shortcuts"
[node name="StatesMessage" type="Label" parent="Runtime"]
layout_mode = 2
text = "If an autoload is enabled here you can refer to its properties and methods without having to use its name."
[node name="StatesHint" type="Label" parent="Runtime"]
modulate = Color(1, 1, 1, 0.501961)
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
text = "ie. Instead of \"SomeState.some_property\" you could just use \"some_property\""
autowrap_mode = 3
[node name="GlobalsList" type="Tree" parent="Runtime"]
layout_mode = 2
size_flags_vertical = 3
columns = 3
column_titles_visible = true
allow_reselect = true
hide_folding = true
hide_root = true
select_mode = 1
[node name="Advanced" type="VBoxContainer" parent="."]
layout_mode = 2
[node name="CheckForUpdates" type="CheckBox" parent="Advanced"]
layout_mode = 2
text = "Check for updates"
[node name="HSeparator" type="HSeparator" parent="Advanced"]
layout_mode = 2
[node name="IncludeCharactersInTranslations" type="CheckBox" parent="Advanced"]
layout_mode = 2
text = "Include character names in translation exports"
[node name="IncludeNotesInTranslations" type="CheckBox" parent="Advanced"]
layout_mode = 2
text = "Include notes (## comments) in translation exports"
[node name="ExternalSeparator" type="HSeparator" parent="Advanced"]
layout_mode = 2
[node name="OpenInExternalEditorButton" type="CheckBox" parent="Advanced"]
layout_mode = 2
text = "Open dialogue files in external editor"
[node name="ExternalWarning" type="Label" parent="Advanced"]
layout_mode = 2
text = "Note: Syntax highlighting and detailed error checking are not supported in external editors."
[node name="HSeparator3" type="HSeparator" parent="Advanced"]
layout_mode = 2
[node name="CustomTestSceneLabel" type="Label" parent="Advanced"]
layout_mode = 2
text = "Custom test scene (must extend BaseDialogueTestScene)"
[node name="CustomTestScene" type="HBoxContainer" parent="Advanced"]
layout_mode = 2
[node name="TestScenePath" type="LineEdit" parent="Advanced/CustomTestScene"]
layout_mode = 2
size_flags_horizontal = 3
focus_mode = 0
placeholder_text = "res://addons/dialogue_manager/test_scene.tscn"
editable = false
shortcut_keys_enabled = false
middle_mouse_paste_enabled = false
[node name="RevertTestScene" type="Button" parent="Advanced/CustomTestScene"]
visible = false
layout_mode = 2
tooltip_text = "Revert to default test scene"
flat = true
[node name="LoadTestScene" type="Button" parent="Advanced/CustomTestScene"]
layout_mode = 2
[node name="HSeparator4" type="HSeparator" parent="Advanced"]
layout_mode = 2
[node name="RecompileWarning" type="Label" parent="Advanced"]
layout_mode = 2
text = "Changing these settings will force a recompile of all dialogue. Only change them if you know what you are doing."
[node name="MissingTranslationsButton" type="CheckBox" parent="Advanced"]
layout_mode = 2
text = "Treat missing translation keys as errors"
[node name="CreateLinesForResponseCharacters" type="CheckBox" parent="Advanced"]
layout_mode = 2
text = "Create child dialogue line for responses with character names in them"
[node name="CustomTestSceneFileDialog" type="FileDialog" parent="."]
title = "Open a File"
ok_button_text = "Open"
file_mode = 0
filters = PackedStringArray("*.tscn ; Scene")
[connection signal="toggled" from="Editor/NewTemplateButton" to="." method="_on_sample_template_toggled"]
[connection signal="toggled" from="Editor/CharactersTranslationsButton" to="." method="_on_characters_translations_button_toggled"]
[connection signal="toggled" from="Editor/WrapLinesButton" to="." method="_on_wrap_lines_button_toggled"]
[connection signal="text_changed" from="Editor/DefaultCSVLocale" to="." method="_on_default_csv_locale_text_changed"]
[connection signal="toggled" from="Runtime/IncludeAllResponsesButton" to="." method="_on_include_all_responses_button_toggled"]
[connection signal="toggled" from="Runtime/IgnoreMissingStateValues" to="." method="_on_ignore_missing_state_values_toggled"]
[connection signal="pressed" from="Runtime/CustomBalloon/RevertBalloonPath" to="." method="_on_revert_balloon_path_pressed"]
[connection signal="pressed" from="Runtime/CustomBalloon/LoadBalloonPath" to="." method="_on_load_balloon_path_pressed"]
[connection signal="button_clicked" from="Runtime/GlobalsList" to="." method="_on_globals_list_button_clicked"]
[connection signal="item_selected" from="Runtime/GlobalsList" to="." method="_on_globals_list_item_selected"]
[connection signal="toggled" from="Advanced/CheckForUpdates" to="." method="_on_keep_up_to_date_toggled"]
[connection signal="toggled" from="Advanced/IncludeCharactersInTranslations" to="." method="_on_include_characters_in_translations_toggled"]
[connection signal="toggled" from="Advanced/IncludeNotesInTranslations" to="." method="_on_include_notes_in_translations_toggled"]
[connection signal="toggled" from="Advanced/OpenInExternalEditorButton" to="." method="_on_open_in_external_editor_button_toggled"]
[connection signal="pressed" from="Advanced/CustomTestScene/RevertTestScene" to="." method="_on_revert_test_scene_pressed"]
[connection signal="pressed" from="Advanced/CustomTestScene/LoadTestScene" to="." method="_on_load_test_scene_pressed"]
[connection signal="toggled" from="Advanced/MissingTranslationsButton" to="." method="_on_missing_translations_button_toggled"]
[connection signal="toggled" from="Advanced/CreateLinesForResponseCharacters" to="." method="_on_create_lines_for_response_characters_toggled"]
[connection signal="file_selected" from="CustomTestSceneFileDialog" to="." method="_on_custom_test_scene_file_dialog_file_selected"]

View File

@ -15,8 +15,23 @@ run/main_scene="res://world.tscn"
config/features=PackedStringArray("4.3", "Forward Plus")
config/icon="res://icon.svg"
[autoload]
DialogueManager="*res://addons/dialogue_manager/dialogue_manager.gd"
[editor_plugins]
enabled=PackedStringArray("res://addons/dialogue_manager/plugin.cfg")
[input]
ui_accept={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194309,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":4194310,"physical_keycode":0,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
, Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
]
}
jump={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null)
@ -32,6 +47,21 @@ move_right={
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
]
}
W={
"deadzone": 0.5,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
]
}
[internationalization]
locale/translations_pot_files=PackedStringArray("res://Dialouges/main.dialogue")
[layer_names]
2d_physics/layer_1="World"
2d_physics/layer_2="Player"
2d_physics/layer_3="Actionables"
[rendering]

View File

@ -1,4 +1,4 @@
[gd_scene load_steps=10 format=3 uid="uid://b0jkivtwisycv"]
[gd_scene load_steps=14 format=3 uid="uid://b0jkivtwisycv"]
[ext_resource type="PackedScene" uid="uid://cny5b638kjd3w" path="res://Assets/Characters/Friendly/Tellik/Tellick.tscn" id="1_dgu6h"]
[ext_resource type="PackedScene" uid="uid://eo08vhsoltt6" path="res://Assets/World/Grounds/Ground1.tscn" id="2_gboim"]
@ -6,6 +6,24 @@
[ext_resource type="PackedScene" uid="uid://bqb3ccnlh1t0s" path="res://Assets/World/Platforms/platform.tscn" id="3_i3imp"]
[ext_resource type="PackedScene" uid="uid://ctysf55pres8y" path="res://Assets/Collectables/coin.tscn" id="4_qkdpl"]
[ext_resource type="PackedScene" uid="uid://cyumvt28wwf28" path="res://Assets/World/kill_zone.tscn" id="6_6a5uv"]
[ext_resource type="PackedScene" uid="uid://bop7ohwaq22g7" path="res://Assets/Characters/Friendly/rock.tscn" id="7_hxqm5"]
[ext_resource type="PackedScene" uid="uid://bbhy8ac5hm1yx" path="res://Dialouges/actionable.tscn" id="8_lg8hl"]
[ext_resource type="Resource" uid="uid://bdqgwj58ijb4o" path="res://Dialouges/main.dialogue" id="9_gx0d7"]
[sub_resource type="Animation" id="Animation_lrrse"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:position")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector2(-645, 31)]
}
[sub_resource type="Animation" id="Animation_ya3iv"]
resource_name = "move"
@ -24,27 +42,14 @@ tracks/0/keys = {
"values": [Vector2(-645, 31), Vector2(-269, 23)]
}
[sub_resource type="Animation" id="Animation_lrrse"]
length = 0.001
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath(".:position")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [Vector2(-645, 31)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_semk0"]
_data = {
"RESET": SubResource("Animation_lrrse"),
"move": SubResource("Animation_ya3iv")
}
[sub_resource type="CircleShape2D" id="CircleShape2D_7llgk"]
[node name="World" type="Node2D"]
physics_interpolation_mode = 1
@ -97,3 +102,13 @@ position = Vector2(518, -127)
[node name="KillZone" parent="." instance=ExtResource("6_6a5uv")]
position = Vector2(-2, 257)
[node name="Rock" parent="." instance=ExtResource("7_hxqm5")]
position = Vector2(-846, -88)
[node name="Actionable" parent="Rock" instance=ExtResource("8_lg8hl")]
dialouge_res = ExtResource("9_gx0d7")
[node name="CollisionShape2D" type="CollisionShape2D" parent="Rock/Actionable"]
position = Vector2(1, 0)
shape = SubResource("CircleShape2D_7llgk")