Movement and Dialougue (#12)
This commit is contained in:
commit
e895a6256e
@ -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="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"]
|
[ext_resource type="Script" path="res://Assets/Characters/Friendly/Tellik/tellick.gd" id="1_q21sg"]
|
||||||
@ -178,6 +178,9 @@ animations = [{
|
|||||||
radius = 21.0
|
radius = 21.0
|
||||||
height = 182.0
|
height = 182.0
|
||||||
|
|
||||||
|
[sub_resource type="RectangleShape2D" id="RectangleShape2D_a2ym7"]
|
||||||
|
size = Vector2(41, 174.5)
|
||||||
|
|
||||||
[node name="Tellick" type="CharacterBody2D"]
|
[node name="Tellick" type="CharacterBody2D"]
|
||||||
z_index = 100
|
z_index = 100
|
||||||
collision_layer = 2
|
collision_layer = 2
|
||||||
@ -191,3 +194,13 @@ autoplay = "default"
|
|||||||
|
|
||||||
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
[node name="CollisionShape2D" type="CollisionShape2D" parent="."]
|
||||||
shape = SubResource("CapsuleShape2D_aoset")
|
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")
|
||||||
|
@ -10,6 +10,14 @@ const FALL_GRAVITY_MULTIPLIER = 2
|
|||||||
var jump_timer = 0.0
|
var jump_timer = 0.0
|
||||||
var is_jumping = false
|
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):
|
func _physics_process(delta):
|
||||||
# Apply gravity
|
# Apply gravity
|
||||||
if not is_on_floor():
|
if not is_on_floor():
|
||||||
|
19
Assets/Characters/Friendly/rock.tscn
Normal file
19
Assets/Characters/Friendly/rock.tscn
Normal 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")
|
BIN
Assets/World/Structures/Ruins_05.png
Normal file
BIN
Assets/World/Structures/Ruins_05.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.8 MiB |
34
Assets/World/Structures/Ruins_05.png.import
Normal file
34
Assets/World/Structures/Ruins_05.png.import
Normal 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
|
7
Dialouges/actionable.tscn
Normal file
7
Dialouges/actionable.tscn
Normal 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
5
Dialouges/main.dialogue
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
~ start
|
||||||
|
|
||||||
|
Rock: OUCH!
|
||||||
|
|
||||||
|
=> END
|
15
Dialouges/main.dialogue.import
Normal file
15
Dialouges/main.dialogue.import
Normal 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
9
Scripts/actionable.gd
Normal 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
21
Scripts/dialouge.gd
Normal 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())
|
423
addons/dialogue_manager/DialogueManager.cs
Normal file
423
addons/dialogue_manager/DialogueManager.cs
Normal 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}\"";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
21
addons/dialogue_manager/LICENSE
Normal file
21
addons/dialogue_manager/LICENSE
Normal 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.
|
52
addons/dialogue_manager/assets/icon.svg
Normal file
52
addons/dialogue_manager/assets/icon.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 7.8 KiB |
38
addons/dialogue_manager/assets/icon.svg.import
Normal file
38
addons/dialogue_manager/assets/icon.svg.import
Normal 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
|
52
addons/dialogue_manager/assets/responses_menu.svg
Normal file
52
addons/dialogue_manager/assets/responses_menu.svg
Normal 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 |
38
addons/dialogue_manager/assets/responses_menu.svg.import
Normal file
38
addons/dialogue_manager/assets/responses_menu.svg.import
Normal 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
|
71
addons/dialogue_manager/assets/update.svg
Normal file
71
addons/dialogue_manager/assets/update.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 11 KiB |
37
addons/dialogue_manager/assets/update.svg.import
Normal file
37
addons/dialogue_manager/assets/update.svg.import
Normal 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
|
444
addons/dialogue_manager/components/code_edit.gd
Normal file
444
addons/dialogue_manager/components/code_edit.gd
Normal 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)
|
56
addons/dialogue_manager/components/code_edit.tscn
Normal file
56
addons/dialogue_manager/components/code_edit.tscn
Normal 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"]
|
@ -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
|
168
addons/dialogue_manager/components/dialogue_cache.gd
Normal file
168
addons/dialogue_manager/components/dialogue_cache.gd
Normal 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()
|
84
addons/dialogue_manager/components/download_update_panel.gd
Normal file
84
addons/dialogue_manager/components/download_update_panel.gd
Normal 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)
|
@ -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"]
|
@ -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)
|
@ -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))
|
@ -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"]
|
@ -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]))
|
@ -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")
|
85
addons/dialogue_manager/components/errors_panel.gd
Normal file
85
addons/dialogue_manager/components/errors_panel.gd
Normal 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()
|
56
addons/dialogue_manager/components/errors_panel.tscn
Normal file
56
addons/dialogue_manager/components/errors_panel.tscn
Normal 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"]
|
146
addons/dialogue_manager/components/files_list.gd
Normal file
146
addons/dialogue_manager/components/files_list.gd
Normal 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)
|
28
addons/dialogue_manager/components/files_list.tscn
Normal file
28
addons/dialogue_manager/components/files_list.tscn
Normal 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"]
|
229
addons/dialogue_manager/components/find_in_files.gd
Normal file
229
addons/dialogue_manager/components/find_in_files.gd
Normal 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
|
139
addons/dialogue_manager/components/find_in_files.tscn
Normal file
139
addons/dialogue_manager/components/find_in_files.tscn
Normal 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"]
|
10
addons/dialogue_manager/components/parse_result.gd
Normal file
10
addons/dialogue_manager/components/parse_result.gd
Normal 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 = ""
|
1798
addons/dialogue_manager/components/parser.gd
Normal file
1798
addons/dialogue_manager/components/parser.gd
Normal file
File diff suppressed because it is too large
Load Diff
15
addons/dialogue_manager/components/resolved_line_data.gd
Normal file
15
addons/dialogue_manager/components/resolved_line_data.gd
Normal 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
|
10
addons/dialogue_manager/components/resolved_tag_data.gd
Normal file
10
addons/dialogue_manager/components/resolved_tag_data.gd
Normal 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
|
212
addons/dialogue_manager/components/search_and_replace.gd
Normal file
212
addons/dialogue_manager/components/search_and_replace.gd
Normal 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()
|
87
addons/dialogue_manager/components/search_and_replace.tscn
Normal file
87
addons/dialogue_manager/components/search_and_replace.tscn
Normal 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"]
|
67
addons/dialogue_manager/components/title_list.gd
Normal file
67
addons/dialogue_manager/components/title_list.gd
Normal 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)
|
27
addons/dialogue_manager/components/title_list.tscn
Normal file
27
addons/dialogue_manager/components/title_list.tscn
Normal 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"]
|
125
addons/dialogue_manager/components/update_button.gd
Normal file
125
addons/dialogue_manager/components/update_button.gd
Normal 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()
|
42
addons/dialogue_manager/components/update_button.tscn
Normal file
42
addons/dialogue_manager/components/update_button.tscn
Normal 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"]
|
189
addons/dialogue_manager/constants.gd
Normal file
189
addons/dialogue_manager/constants.gd
Normal 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)
|
230
addons/dialogue_manager/dialogue_label.gd
Normal file
230
addons/dialogue_manager/dialogue_label.gd
Normal 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()
|
19
addons/dialogue_manager/dialogue_label.tscn
Normal file
19
addons/dialogue_manager/dialogue_label.tscn
Normal 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")
|
98
addons/dialogue_manager/dialogue_line.gd
Normal file
98
addons/dialogue_manager/dialogue_line.gd
Normal 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 ""
|
1274
addons/dialogue_manager/dialogue_manager.gd
Normal file
1274
addons/dialogue_manager/dialogue_manager.gd
Normal file
File diff suppressed because it is too large
Load Diff
141
addons/dialogue_manager/dialogue_reponses_menu.gd
Normal file
141
addons/dialogue_manager/dialogue_reponses_menu.gd
Normal 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
|
43
addons/dialogue_manager/dialogue_resource.gd
Normal file
43
addons/dialogue_manager/dialogue_resource.gd
Normal 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())]
|
62
addons/dialogue_manager/dialogue_response.gd
Normal file
62
addons/dialogue_manager/dialogue_response.gd
Normal 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 ""
|
44
addons/dialogue_manager/editor_translation_parser_plugin.gd
Normal file
44
addons/dialogue_manager/editor_translation_parser_plugin.gd
Normal 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"]
|
219
addons/dialogue_manager/example_balloon/ExampleBalloon.cs
Normal file
219
addons/dialogue_manager/example_balloon/ExampleBalloon.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
156
addons/dialogue_manager/example_balloon/example_balloon.gd
Normal file
156
addons/dialogue_manager/example_balloon/example_balloon.gd
Normal 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
|
149
addons/dialogue_manager/example_balloon/example_balloon.tscn
Normal file
149
addons/dialogue_manager/example_balloon/example_balloon.tscn
Normal 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"]
|
@ -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"]
|
116
addons/dialogue_manager/import_plugin.gd
Normal file
116
addons/dialogue_manager/import_plugin.gd
Normal 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
|
21
addons/dialogue_manager/inspector_plugin.gd
Normal file
21
addons/dialogue_manager/inspector_plugin.gd
Normal 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
|
BIN
addons/dialogue_manager/l10n/en.mo
Normal file
BIN
addons/dialogue_manager/l10n/en.mo
Normal file
Binary file not shown.
481
addons/dialogue_manager/l10n/en.po
Normal file
481
addons/dialogue_manager/l10n/en.po
Normal 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."
|
457
addons/dialogue_manager/l10n/es.po
Normal file
457
addons/dialogue_manager/l10n/es.po
Normal 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\"."
|
471
addons/dialogue_manager/l10n/translations.pot
Normal file
471
addons/dialogue_manager/l10n/translations.pot
Normal 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 ""
|
480
addons/dialogue_manager/l10n/uk.po
Normal file
480
addons/dialogue_manager/l10n/uk.po
Normal 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»."
|
447
addons/dialogue_manager/l10n/zh.po
Normal file
447
addons/dialogue_manager/l10n/zh.po
Normal 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 "有什么出错了。"
|
447
addons/dialogue_manager/l10n/zh_TW.po
Normal file
447
addons/dialogue_manager/l10n/zh_TW.po
Normal 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 "有什麼出錯了。"
|
7
addons/dialogue_manager/plugin.cfg
Normal file
7
addons/dialogue_manager/plugin.cfg
Normal 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"
|
363
addons/dialogue_manager/plugin.gd
Normal file
363
addons/dialogue_manager/plugin.gd
Normal 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)
|
187
addons/dialogue_manager/settings.gd
Normal file
187
addons/dialogue_manager/settings.gd
Normal 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)
|
32
addons/dialogue_manager/test_scene.gd
Normal file
32
addons/dialogue_manager/test_scene.gd
Normal 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()
|
7
addons/dialogue_manager/test_scene.tscn
Normal file
7
addons/dialogue_manager/test_scene.tscn
Normal 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")
|
471
addons/dialogue_manager/utilities/builtins.gd
Normal file
471
addons/dialogue_manager/utilities/builtins.gd
Normal 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]
|
1134
addons/dialogue_manager/views/main_view.gd
Normal file
1134
addons/dialogue_manager/views/main_view.gd
Normal file
File diff suppressed because it is too large
Load Diff
431
addons/dialogue_manager/views/main_view.tscn
Normal file
431
addons/dialogue_manager/views/main_view.tscn
Normal 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"]
|
280
addons/dialogue_manager/views/settings_view.gd
Normal file
280
addons/dialogue_manager/views/settings_view.gd
Normal 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)
|
221
addons/dialogue_manager/views/settings_view.tscn
Normal file
221
addons/dialogue_manager/views/settings_view.tscn
Normal 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"]
|
@ -18,8 +18,23 @@ run/main_scene="res://world.tscn"
|
|||||||
config/features=PackedStringArray("4.3", "Forward Plus")
|
config/features=PackedStringArray("4.3", "Forward Plus")
|
||||||
config/icon="res://icon.svg"
|
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]
|
[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={
|
jump={
|
||||||
"deadzone": 0.5,
|
"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)
|
"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)
|
||||||
@ -35,6 +50,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)
|
"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]
|
[rendering]
|
||||||
|
|
||||||
|
47
world.tscn
47
world.tscn
@ -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://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"]
|
[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://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://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://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"]
|
[sub_resource type="Animation" id="Animation_ya3iv"]
|
||||||
resource_name = "move"
|
resource_name = "move"
|
||||||
@ -24,27 +42,14 @@ tracks/0/keys = {
|
|||||||
"values": [Vector2(-645, 31), Vector2(-269, 23)]
|
"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"]
|
[sub_resource type="AnimationLibrary" id="AnimationLibrary_semk0"]
|
||||||
_data = {
|
_data = {
|
||||||
"RESET": SubResource("Animation_lrrse"),
|
"RESET": SubResource("Animation_lrrse"),
|
||||||
"move": SubResource("Animation_ya3iv")
|
"move": SubResource("Animation_ya3iv")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[sub_resource type="CircleShape2D" id="CircleShape2D_7llgk"]
|
||||||
|
|
||||||
[node name="World" type="Node2D"]
|
[node name="World" type="Node2D"]
|
||||||
physics_interpolation_mode = 1
|
physics_interpolation_mode = 1
|
||||||
|
|
||||||
@ -97,3 +102,13 @@ position = Vector2(518, -127)
|
|||||||
|
|
||||||
[node name="KillZone" parent="." instance=ExtResource("6_6a5uv")]
|
[node name="KillZone" parent="." instance=ExtResource("6_6a5uv")]
|
||||||
position = Vector2(-2, 257)
|
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")
|
||||||
|
Loading…
Reference in New Issue
Block a user