dialogue manager addon
parent
8ef18b0ae1
commit
942a17981c
|
@ -0,0 +1,144 @@
|
|||
using Godot;
|
||||
using Godot.Collections;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace DialogueManagerRuntime
|
||||
{
|
||||
public partial class DialogueManager : Node
|
||||
{
|
||||
public static async Task<DialogueLine> GetNextDialogueLine(Resource dialogueResource, string key = "0", Array<Variant> extraGameStates = null)
|
||||
{
|
||||
var dialogueManager = Engine.GetSingleton("DialogueManager");
|
||||
dialogueManager.Call("_bridge_get_next_dialogue_line", dialogueResource, key, extraGameStates ?? new Array<Variant>());
|
||||
var result = await dialogueManager.ToSignal(dialogueManager, "bridge_get_next_dialogue_line_completed");
|
||||
|
||||
if ((RefCounted)result[0] == null) return null;
|
||||
|
||||
return new DialogueLine((RefCounted)result[0]);
|
||||
}
|
||||
|
||||
|
||||
public static void ShowExampleDialogueBalloon(Resource dialogueResource, string key = "0", Array<Variant> extraGameStates = null)
|
||||
{
|
||||
Engine.GetSingleton("DialogueManager").Call("show_example_dialogue_balloon", dialogueResource, key, extraGameStates ?? new Array<Variant>());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public partial class DialogueLine : RefCounted
|
||||
{
|
||||
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();
|
||||
private Dictionary speeds = new Dictionary();
|
||||
|
||||
private Array<Array> inline_mutations = new Array<Array>();
|
||||
|
||||
private Array<Variant> extra_game_states = new Array<Variant>();
|
||||
|
||||
|
||||
|
||||
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<Array>)data.Get("inline_mutations");
|
||||
|
||||
foreach (var response in (Array<RefCounted>)data.Get("responses"))
|
||||
{
|
||||
responses.Add(new DialogueResponse(response));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022-present Nathan Hoad
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 7.8 KiB |
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://d3lr2uas6ax8v"
|
||||
path="res://.godot/imported/icon.svg-17eb5d3e2a3cfbe59852220758c5b7bd.ctex"
|
||||
metadata={
|
||||
"editor_dark_theme": true,
|
||||
"editor_scale": 1.0,
|
||||
"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
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 11 KiB |
|
@ -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
|
|
@ -0,0 +1,439 @@
|
|||
@tool
|
||||
extends CodeEdit
|
||||
|
||||
|
||||
signal active_title_change(title: String)
|
||||
signal error_clicked(line_number: int)
|
||||
signal external_file_requested(path: String, title: String)
|
||||
|
||||
|
||||
# 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.clear_color_regions()
|
||||
syntax_highlighter.clear_keyword_colors()
|
||||
|
||||
# Imports
|
||||
syntax_highlighter.add_keyword_color("import", theme_overrides.conditions_color)
|
||||
syntax_highlighter.add_keyword_color("as", theme_overrides.conditions_color)
|
||||
|
||||
# Titles
|
||||
syntax_highlighter.add_color_region("~", "~", theme_overrides.titles_color, true)
|
||||
|
||||
# Comments
|
||||
syntax_highlighter.add_color_region("#", "##", theme_overrides.comments_color, true)
|
||||
|
||||
# Conditions
|
||||
syntax_highlighter.add_keyword_color("if", theme_overrides.conditions_color)
|
||||
syntax_highlighter.add_keyword_color("elif", theme_overrides.conditions_color)
|
||||
syntax_highlighter.add_keyword_color("else", theme_overrides.conditions_color)
|
||||
syntax_highlighter.add_keyword_color("while", theme_overrides.conditions_color)
|
||||
syntax_highlighter.add_keyword_color("endif", theme_overrides.conditions_color)
|
||||
syntax_highlighter.add_keyword_color("in", theme_overrides.conditions_color)
|
||||
syntax_highlighter.add_keyword_color("and", theme_overrides.conditions_color)
|
||||
syntax_highlighter.add_keyword_color("or", theme_overrides.conditions_color)
|
||||
syntax_highlighter.add_keyword_color("not", theme_overrides.conditions_color)
|
||||
|
||||
# Values
|
||||
syntax_highlighter.add_keyword_color("true", theme_overrides.numbers_color)
|
||||
syntax_highlighter.add_keyword_color("false", theme_overrides.numbers_color)
|
||||
syntax_highlighter.number_color = theme_overrides.numbers_color
|
||||
syntax_highlighter.add_color_region("\"", "\"", theme_overrides.strings_color)
|
||||
|
||||
# Mutations
|
||||
syntax_highlighter.add_keyword_color("do", theme_overrides.mutations_color)
|
||||
syntax_highlighter.add_keyword_color("set", theme_overrides.mutations_color)
|
||||
syntax_highlighter.function_color = theme_overrides.mutations_color
|
||||
syntax_highlighter.member_variable_color = theme_overrides.members_color
|
||||
|
||||
# Jumps
|
||||
syntax_highlighter.add_color_region("=>", "<=", theme_overrides.jumps_color, true)
|
||||
|
||||
# Dialogue
|
||||
syntax_highlighter.add_color_region(": ", "::", theme_overrides.text_color, true)
|
||||
|
||||
# General UI
|
||||
syntax_highlighter.symbol_color = theme_overrides.symbols_color
|
||||
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
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
func _gui_input(event: InputEvent) -> void:
|
||||
if event is InputEventKey and event.is_pressed():
|
||||
match event.as_text():
|
||||
"Ctrl+Equal", "Command+Equal":
|
||||
self.font_size += 1
|
||||
get_viewport().set_input_as_handled()
|
||||
"Ctrl+Minus", "Command+Minus":
|
||||
self.font_size -= 1
|
||||
get_viewport().set_input_as_handled()
|
||||
"Ctrl+0", "Command+0":
|
||||
self.font_size = theme_overrides.font_size
|
||||
get_viewport().set_input_as_handled()
|
||||
"Ctrl+K", "Command+K":
|
||||
toggle_comment()
|
||||
get_viewport().set_input_as_handled()
|
||||
"Alt+Up":
|
||||
move_line(-1)
|
||||
get_viewport().set_input_as_handled()
|
||||
"Alt+Down":
|
||||
move_line(1)
|
||||
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 last_character: String = current_line.substr(cursor.x - 1, 1)
|
||||
var name_so_far: String = 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("~ "):
|
||||
emit_signal("active_title_change", lines[i].replace("~ ", ""))
|
||||
return
|
||||
|
||||
emit_signal("active_title_change", "0")
|
||||
|
||||
|
||||
# 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 = 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(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:
|
||||
# Starting complex operation so that the entire toggle comment can be undone in a single step
|
||||
begin_complex_operation()
|
||||
|
||||
var caret_count: int = get_caret_count()
|
||||
var caret_offsets: Dictionary = {}
|
||||
|
||||
for caret_index in caret_count:
|
||||
var caret_line: int = get_caret_line(caret_index)
|
||||
var from: int = caret_line
|
||||
var to: int = caret_line
|
||||
|
||||
if has_selection(caret_index):
|
||||
from = get_selection_from_line(caret_index)
|
||||
to = get_selection_to_line(caret_index)
|
||||
|
||||
for line in range(from, to + 1):
|
||||
if line not in caret_offsets:
|
||||
caret_offsets[line] = 0
|
||||
|
||||
var line_text: String = get_line(line)
|
||||
var comment_delimiter: String = delimiter_comments[0]
|
||||
var is_line_commented: bool = line_text.begins_with(comment_delimiter)
|
||||
set_line(line, line_text.substr(comment_delimiter.length()) if is_line_commented else comment_delimiter + line_text)
|
||||
caret_offsets[line] += (-1 if is_line_commented else 1) * comment_delimiter.length()
|
||||
|
||||
emit_signal("lines_edited_from", from, to)
|
||||
|
||||
# Readjust carets and selection positions after all carets effect have been calculated
|
||||
# Tried making it in the above loop, but that causes a weird behaviour if two carets are on the same line (first caret will move, but not the second one)
|
||||
for caret_index in caret_count:
|
||||
if has_selection(caret_index):
|
||||
var from: int = get_selection_from_line(caret_index)
|
||||
var to: int = get_selection_to_line(caret_index)
|
||||
select(from, get_selection_from_column(caret_index) + caret_offsets[from], to, get_selection_to_column(caret_index) + caret_offsets[to], caret_index)
|
||||
|
||||
set_caret_column(get_caret_column(caret_index) + caret_offsets[get_caret_line(caret_index)], true, caret_index)
|
||||
|
||||
end_complex_operation()
|
||||
|
||||
emit_signal("text_set")
|
||||
emit_signal("text_changed")
|
||||
|
||||
|
||||
# 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")
|
||||
|
||||
# We can't move the lines 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)
|
||||
emit_signal("text_changed")
|
||||
|
||||
|
||||
### 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"):
|
||||
emit_signal("external_file_requested", 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:
|
||||
emit_signal("error_clicked", line)
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,85 @@
|
|||
@tool
|
||||
extends Control
|
||||
|
||||
|
||||
signal failed()
|
||||
signal updated(updated_to_version: String)
|
||||
|
||||
|
||||
const DialogueConstants = preload("res://addons/dialogue_manager/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()
|
||||
|
||||
if DirAccess.dir_exists_absolute("res://addons/dialogue_manager"):
|
||||
DirAccess.remove_absolute("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 and install 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,83 @@
|
|||
@tool
|
||||
extends HBoxContainer
|
||||
|
||||
|
||||
signal error_pressed(line_number)
|
||||
|
||||
|
||||
const DialogueConstants = preload("res://addons/dialogue_manager/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) })
|
||||
|
||||
|
||||
### 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()
|
|
@ -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"]
|
|
@ -0,0 +1,135 @@
|
|||
@tool
|
||||
extends VBoxContainer
|
||||
|
||||
|
||||
signal file_selected(file_path: String)
|
||||
signal file_popup_menu_requested(at_position: Vector2)
|
||||
signal file_double_clicked(file_path: String)
|
||||
|
||||
|
||||
const DialogueConstants = preload("res://addons/dialogue_manager/constants.gd")
|
||||
|
||||
const MODIFIED_SUFFIX = "(*)"
|
||||
|
||||
|
||||
@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 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
|
||||
list.add_item(nice_file)
|
||||
|
||||
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:
|
||||
if mouse_button_index == MOUSE_BUTTON_LEFT:
|
||||
var item_text = list.get_item_text(index).replace(MODIFIED_SUFFIX, "")
|
||||
var file = file_map.find_key(item_text)
|
||||
select_file(file)
|
||||
file_selected.emit(file)
|
||||
|
||||
if mouse_button_index == MOUSE_BUTTON_RIGHT:
|
||||
file_popup_menu_requested.emit(at_position)
|
||||
|
||||
|
||||
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)
|
|
@ -0,0 +1,38 @@
|
|||
[gd_scene load_steps=4 format=3 uid="uid://dnufpcdrreva3"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/dialogue_manager/components/files_list.gd" id="1_cytii"]
|
||||
|
||||
[sub_resource type="Image" id="Image_mirhx"]
|
||||
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_wy68i"]
|
||||
image = SubResource("Image_mirhx")
|
||||
|
||||
[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")
|
||||
|
||||
[node name="FilterEdit" type="LineEdit" parent="."]
|
||||
layout_mode = 2
|
||||
placeholder_text = "Filter files"
|
||||
clear_button_enabled = true
|
||||
right_icon = SubResource("ImageTexture_wy68i")
|
||||
|
||||
[node name="List" type="ItemList" parent="."]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[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"]
|
|
@ -0,0 +1,7 @@
|
|||
class_name DialogueManagerParseResult extends RefCounted
|
||||
|
||||
var imported_paths: PackedStringArray = []
|
||||
var titles: Dictionary = {}
|
||||
var character_names: PackedStringArray = []
|
||||
var first_title: String = ""
|
||||
var lines: Dictionary = {}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,196 @@
|
|||
@tool
|
||||
extends VBoxContainer
|
||||
|
||||
|
||||
signal open_requested()
|
||||
signal close_requested()
|
||||
|
||||
|
||||
const DialogueConstants = preload("res://addons/dialogue_manager/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_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()
|
||||
|
||||
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 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() and event.as_text() == "Ctrl+F":
|
||||
emit_signal("open_requested")
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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()
|
||||
|
||||
|
||||
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()
|
|
@ -0,0 +1,97 @@
|
|||
[gd_scene load_steps=4 format=3 uid="uid://gr8nakpbrhby"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/dialogue_manager/components/search_and_replace.gd" id="1_8oj1f"]
|
||||
|
||||
[sub_resource type="Image" id="Image_mirhx"]
|
||||
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_wy68i"]
|
||||
image = SubResource("Image_mirhx")
|
||||
|
||||
[node name="SearchAndReplace" type="VBoxContainer"]
|
||||
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
|
||||
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
|
||||
icon = SubResource("ImageTexture_wy68i")
|
||||
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
|
||||
icon = SubResource("ImageTexture_wy68i")
|
||||
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"]
|
|
@ -0,0 +1,137 @@
|
|||
@tool
|
||||
extends Node
|
||||
|
||||
|
||||
const DialogueConstants = preload("res://addons/dialogue_manager/constants.gd")
|
||||
|
||||
|
||||
### Editor config
|
||||
|
||||
const DEFAULT_SETTINGS = {
|
||||
"states" = [],
|
||||
"missing_translations_are_errors" = false,
|
||||
"wrap_lines" = false,
|
||||
"new_with_template" = true,
|
||||
"include_all_responses" = false,
|
||||
"custom_test_scene_path" = "res://addons/dialogue_manager/test_scene.tscn"
|
||||
}
|
||||
|
||||
|
||||
static func prepare() -> void:
|
||||
# Migrate previous keys
|
||||
for key in [
|
||||
"states",
|
||||
"missing_translations_are_errors",
|
||||
"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 defaults
|
||||
for setting in DEFAULT_SETTINGS:
|
||||
if ProjectSettings.has_setting("dialogue_manager/general/%s" % setting):
|
||||
ProjectSettings.set_initial_value("dialogue_manager/general/%s" % setting, DEFAULT_SETTINGS[setting])
|
||||
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
|
||||
|
||||
|
||||
### User config
|
||||
|
||||
|
||||
static func get_user_config() -> Dictionary:
|
||||
var user_config: Dictionary = {
|
||||
just_refreshed = null,
|
||||
recent_files = [],
|
||||
carets = {},
|
||||
run_title = "",
|
||||
run_resource_path = "",
|
||||
is_running_test_scene = 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
|
|
@ -0,0 +1,67 @@
|
|||
@tool
|
||||
extends VBoxContainer
|
||||
|
||||
signal title_selected(title: String)
|
||||
|
||||
|
||||
const DialogueConstants = preload("res://addons/dialogue_manager/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)
|
|
@ -0,0 +1,45 @@
|
|||
[gd_scene load_steps=4 format=3 uid="uid://ctns6ouwwd68i"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/dialogue_manager/components/title_list.gd" id="1_5qqmd"]
|
||||
|
||||
[sub_resource type="Image" id="Image_o5dqs"]
|
||||
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_ekmpw"]
|
||||
image = SubResource("Image_o5dqs")
|
||||
|
||||
[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
|
||||
offset_right = 1152.0
|
||||
offset_bottom = 31.0
|
||||
placeholder_text = "Filter titles"
|
||||
clear_button_enabled = true
|
||||
right_icon = SubResource("ImageTexture_ekmpw")
|
||||
|
||||
[node name="List" type="ItemList" parent="."]
|
||||
layout_mode = 2
|
||||
offset_top = 35.0
|
||||
offset_right = 1152.0
|
||||
offset_bottom = 648.0
|
||||
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"]
|
|
@ -0,0 +1,132 @@
|
|||
@tool
|
||||
extends Button
|
||||
|
||||
const DialogueConstants = preload("res://addons/dialogue_manager/constants.gd")
|
||||
|
||||
const REMOTE_RELEASES_URL = "https://api.github.com/repos/nathanhoad/godot_dialogue_manager/releases"
|
||||
const LOCAL_CONFIG_PATH = "res://addons/dialogue_manager/plugin.cfg"
|
||||
|
||||
|
||||
@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
|
||||
|
||||
# The main editor plugin
|
||||
var editor_plugin: EditorPlugin
|
||||
|
||||
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)
|
||||
|
||||
|
||||
# Get the current version
|
||||
func get_version() -> String:
|
||||
var config: ConfigFile = ConfigFile.new()
|
||||
config.load(LOCAL_CONFIG_PATH)
|
||||
return config.get_value("plugin", "version")
|
||||
|
||||
|
||||
# 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:
|
||||
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 = 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)
|
||||
return 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:
|
||||
editor_plugin.get_editor_interface().restart_editor(true)
|
||||
else:
|
||||
var scale: float = editor_plugin.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:
|
||||
editor_plugin.get_editor_interface().restart_editor(true)
|
||||
|
||||
|
||||
func _on_timer_timeout() -> void:
|
||||
if not needs_reload:
|
||||
check_for_update()
|
|
@ -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"]
|
|
@ -0,0 +1,181 @@
|
|||
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
|
||||
|
||||
|
||||
## 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")
|
||||
|
||||
return translate("errors.unknown")
|
||||
|
||||
|
||||
static func translate(string: String) -> String:
|
||||
var language: String = TranslationServer.get_tool_locale().substr(0, 2)
|
||||
var translations_path: String = "res://addons/dialogue_manager/l10n/%s.po" % language
|
||||
var fallback_translations_path: String = "res://addons/dialogue_manager/l10n/en.po"
|
||||
var translations: Translation = load(translations_path if FileAccess.file_exists(translations_path) else fallback_translations_path)
|
||||
return translations.get_message(string)
|
|
@ -0,0 +1,139 @@
|
|||
extends RichTextLabel
|
||||
|
||||
|
||||
signal spoke(letter: String, letter_index: int, speed: float)
|
||||
signal paused_typing(duration: float)
|
||||
signal finished_typing()
|
||||
|
||||
|
||||
## The action to press to skip typing
|
||||
@export var skip_action: String = "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 = ".?!"
|
||||
|
||||
|
||||
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
|
||||
|
||||
var last_wait_index: int = -1
|
||||
var last_mutation_index: int = -1
|
||||
var waiting_seconds: float = 0
|
||||
|
||||
var is_typing: bool = false:
|
||||
set(value):
|
||||
if is_typing != value and value == false:
|
||||
finished_typing.emit()
|
||||
is_typing = value
|
||||
get:
|
||||
return is_typing
|
||||
|
||||
|
||||
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:
|
||||
self.is_typing = false
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if self.is_typing and visible_ratio < 1 and event.is_action_pressed(skip_action):
|
||||
# Run any inline mutations that haven't been run yet
|
||||
for i in range(visible_characters, get_total_character_count()):
|
||||
mutate_inline_mutations(i)
|
||||
visible_characters = get_total_character_count()
|
||||
self.is_typing = false
|
||||
finished_typing.emit()
|
||||
|
||||
|
||||
# Start typing out the text
|
||||
func type_out() -> void:
|
||||
text = dialogue_line.text
|
||||
visible_characters = 0
|
||||
self.is_typing = true
|
||||
waiting_seconds = 0
|
||||
|
||||
# Text isn't calculated until the next frame
|
||||
await get_tree().process_frame
|
||||
|
||||
if get_total_character_count() == 0:
|
||||
self.is_typing = false
|
||||
elif seconds_per_step == 0:
|
||||
# Run any inline mutations
|
||||
for i in range(0, get_total_character_count()):
|
||||
mutate_inline_mutations(i)
|
||||
visible_characters = get_total_character_count()
|
||||
self.is_typing = false
|
||||
|
||||
|
||||
# Type out the next character(s)
|
||||
func type_next(delta: float, seconds_needed: float) -> void:
|
||||
if visible_characters == get_total_character_count():
|
||||
return
|
||||
|
||||
if last_mutation_index != visible_characters:
|
||||
last_mutation_index = visible_characters
|
||||
mutate_inline_mutations(visible_characters)
|
||||
|
||||
var additional_waiting_seconds: float = get_pause(visible_characters)
|
||||
|
||||
# Pause on characters like "."
|
||||
if visible_characters > 0 and get_parsed_text()[visible_characters - 1] in pause_at_characters.split():
|
||||
additional_waiting_seconds += seconds_per_step * 15
|
||||
|
||||
# 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 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:
|
||||
# The DialogueManager can't be referenced directly here so we need to get it by its path
|
||||
Engine.get_singleton("DialogueManager").mutate(inline_mutation[1], dialogue_line.extra_game_states, true)
|
|
@ -0,0 +1,18 @@
|
|||
[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")
|
|
@ -0,0 +1,43 @@
|
|||
class_name DialogueLine extends RefCounted
|
||||
|
||||
|
||||
const DialogueConstants = preload("res://addons/dialogue_manager/constants.gd")
|
||||
const DialogueResponse = preload("res://addons/dialogue_manager/dialogue_response.gd")
|
||||
|
||||
|
||||
var type: String = DialogueConstants.TYPE_DIALOGUE
|
||||
var next_id: String = ""
|
||||
var character: String = ""
|
||||
var character_replacements: Array[Dictionary] = []
|
||||
var text: String = ""
|
||||
var text_replacements: Array[Dictionary] = []
|
||||
var translation_key: String = ""
|
||||
var pauses: Dictionary = {}
|
||||
var speeds: Dictionary = {}
|
||||
var inline_mutations: Array[Array] = []
|
||||
var responses: Array[DialogueResponse] = []
|
||||
var extra_game_states: Array = []
|
||||
var time = null
|
||||
var mutation: Dictionary = {}
|
||||
|
||||
|
||||
func _init(data: Dictionary = {}) -> void:
|
||||
if data.size() > 0:
|
||||
next_id = data.next_id
|
||||
type = data.type
|
||||
extra_game_states = data.extra_game_states
|
||||
|
||||
match type:
|
||||
DialogueConstants.TYPE_DIALOGUE:
|
||||
character = data.character
|
||||
character_replacements = data.character_replacements
|
||||
text = data.text
|
||||
text_replacements = data.text_replacements
|
||||
translation_key = data.translation_key
|
||||
pauses = data.pauses
|
||||
speeds = data.speeds
|
||||
inline_mutations = data.inline_mutations
|
||||
time = data.time
|
||||
|
||||
DialogueConstants.TYPE_MUTATION:
|
||||
mutation = data.mutation
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,20 @@
|
|||
@icon("./assets/icon.svg")
|
||||
|
||||
class_name DialogueResource extends Resource
|
||||
|
||||
|
||||
const DialogueManager = preload("res://addons/dialogue_manager/dialogue_manager.gd")
|
||||
|
||||
|
||||
@export var titles: Dictionary = {}
|
||||
@export var character_names: PackedStringArray = []
|
||||
@export var first_title: String = ""
|
||||
@export var lines: Dictionary = {}
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
func get_titles() -> PackedStringArray:
|
||||
return titles.keys()
|
|
@ -0,0 +1,22 @@
|
|||
class_name DialogueResponse extends RefCounted
|
||||
|
||||
|
||||
const DialogueConstants = preload("res://addons/dialogue_manager/constants.gd")
|
||||
|
||||
|
||||
var type: String = DialogueConstants.TYPE_RESPONSE
|
||||
var next_id: String = ""
|
||||
var is_allowed: bool = true
|
||||
var text: String = ""
|
||||
var text_replacements: Array[Dictionary] = []
|
||||
var translation_key: String = ""
|
||||
|
||||
|
||||
func _init(data: Dictionary = {}) -> void:
|
||||
if data.size() > 0:
|
||||
type = data.type
|
||||
next_id = data.next_id
|
||||
is_allowed = data.is_allowed
|
||||
text = data.text
|
||||
text_replacements = data.text_replacements
|
||||
translation_key = data.translation_key
|
|
@ -0,0 +1,40 @@
|
|||
extends EditorTranslationParserPlugin
|
||||
|
||||
|
||||
const DialogueConstants = preload("res://addons/dialogue_manager/constants.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
|
||||
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, "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, "", ""])
|
||||
else:
|
||||
msgids_context_plural.append([line.text, line.translation_key, ""])
|
||||
|
||||
|
||||
func _get_recognized_extensions() -> PackedStringArray:
|
||||
return ["dialogue"]
|
|
@ -0,0 +1,212 @@
|
|||
extends CanvasLayer
|
||||
|
||||
|
||||
@onready var balloon: ColorRect = $Balloon
|
||||
@onready var margin: MarginContainer = $Balloon/Margin
|
||||
@onready var character_label: RichTextLabel = $Balloon/Margin/VBox/CharacterLabel
|
||||
@onready var dialogue_label := $Balloon/Margin/VBox/DialogueLabel
|
||||
@onready var responses_menu: VBoxContainer = $Balloon/Margin/VBox/Responses
|
||||
@onready var response_template: RichTextLabel = %ResponseTemplate
|
||||
|
||||
## 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
|
||||
|
||||
## The current line
|
||||
var dialogue_line: DialogueLine:
|
||||
set(next_dialogue_line):
|
||||
is_waiting_for_input = false
|
||||
|
||||
if not next_dialogue_line:
|
||||
queue_free()
|
||||
return
|
||||
|
||||
# Remove any previous responses
|
||||
for child in responses_menu.get_children():
|
||||
responses_menu.remove_child(child)
|
||||
child.queue_free()
|
||||
|
||||
dialogue_line = next_dialogue_line
|
||||
|
||||
character_label.visible = not dialogue_line.character.is_empty()
|
||||
character_label.text = tr(dialogue_line.character, "dialogue")
|
||||
|
||||
dialogue_label.modulate.a = 0
|
||||
dialogue_label.custom_minimum_size.x = dialogue_label.get_parent().size.x - 1
|
||||
dialogue_label.dialogue_line = dialogue_line
|
||||
|
||||
# Show any responses we have
|
||||
responses_menu.modulate.a = 0
|
||||
if dialogue_line.responses.size() > 0:
|
||||
for response in dialogue_line.responses:
|
||||
# Duplicate the template so we can grab the fonts, sizing, etc
|
||||
var item: RichTextLabel = response_template.duplicate(0)
|
||||
item.name = "Response%d" % responses_menu.get_child_count()
|
||||
if not response.is_allowed:
|
||||
item.name = String(item.name) + "Disallowed"
|
||||
item.modulate.a = 0.4
|
||||
item.text = response.text
|
||||
item.show()
|
||||
responses_menu.add_child(item)
|
||||
|
||||
# Show our balloon
|
||||
balloon.show()
|
||||
will_hide_balloon = false
|
||||
|
||||
dialogue_label.modulate.a = 1
|
||||
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:
|
||||
responses_menu.modulate.a = 1
|
||||
configure_menu()
|
||||
elif dialogue_line.time != null:
|
||||
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:
|
||||
response_template.hide()
|
||||
balloon.hide()
|
||||
balloon.custom_minimum_size.x = balloon.get_viewport_rect().size.x
|
||||
|
||||
Engine.get_singleton("DialogueManager").mutated.connect(_on_mutated)
|
||||
|
||||
|
||||
func _unhandled_input(_event: InputEvent) -> void:
|
||||
# Only the balloon is allowed to handle input while it's showing
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
## Start some dialogue
|
||||
func start(dialogue_resource: DialogueResource, title: String, extra_game_states: Array = []) -> void:
|
||||
temporary_game_states = 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)
|
||||
|
||||
|
||||
### Helpers
|
||||
|
||||
|
||||
# Set up keyboard movement and signals for the response menu
|
||||
func configure_menu() -> void:
|
||||
balloon.focus_mode = Control.FOCUS_NONE
|
||||
|
||||
var items = get_responses()
|
||||
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))
|
||||
|
||||
items[0].grab_focus()
|
||||
|
||||
|
||||
# Get a list of enabled items
|
||||
func get_responses() -> Array:
|
||||
var items: Array = []
|
||||
for child in responses_menu.get_children():
|
||||
if "Disallowed" in child.name: continue
|
||||
items.append(child)
|
||||
|
||||
return items
|
||||
|
||||
|
||||
func handle_resize() -> void:
|
||||
if not is_instance_valid(margin):
|
||||
call_deferred("handle_resize")
|
||||
return
|
||||
|
||||
balloon.custom_minimum_size.y = margin.size.y
|
||||
# Force a resize on only the height
|
||||
balloon.size.y = 0
|
||||
var viewport_size = balloon.get_viewport_rect().size
|
||||
balloon.global_position = Vector2((viewport_size.x - balloon.size.x) * 0.5, viewport_size.y - balloon.size.y)
|
||||
|
||||
|
||||
### 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_response_mouse_entered(item: Control) -> void:
|
||||
if "Disallowed" in item.name: return
|
||||
|
||||
item.grab_focus()
|
||||
|
||||
|
||||
func _on_response_gui_input(event: InputEvent, item: Control) -> void:
|
||||
if "Disallowed" in item.name: return
|
||||
|
||||
if event is InputEventMouseButton and event.is_pressed() and event.button_index == 1:
|
||||
next(dialogue_line.responses[item.get_index()].next_id)
|
||||
elif event.is_action_pressed("ui_accept") and item in get_responses():
|
||||
next(dialogue_line.responses[item.get_index()].next_id)
|
||||
|
||||
|
||||
func _on_balloon_gui_input(event: InputEvent) -> void:
|
||||
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 == 1:
|
||||
next(dialogue_line.next_id)
|
||||
elif event.is_action_pressed("ui_accept") and get_viewport().gui_get_focus_owner() == balloon:
|
||||
next(dialogue_line.next_id)
|
||||
|
||||
|
||||
func _on_margin_resized() -> void:
|
||||
handle_resize()
|
|
@ -0,0 +1,73 @@
|
|||
[gd_scene load_steps=5 format=3 uid="uid://73jm5qjy52vq"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/dialogue_manager/example_balloon/example_balloon.gd" id="1_4u26j"]
|
||||
[ext_resource type="PackedScene" uid="uid://ckvgyvclnwggo" path="res://addons/dialogue_manager/dialogue_label.tscn" id="2_a8ve6"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_5d24i"]
|
||||
content_margin_left = 40.0
|
||||
content_margin_top = 5.0
|
||||
content_margin_right = 5.0
|
||||
content_margin_bottom = 5.0
|
||||
bg_color = Color(1, 1, 1, 0.25098)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_oj3c8"]
|
||||
content_margin_left = 40.0
|
||||
content_margin_top = 5.0
|
||||
content_margin_right = 5.0
|
||||
content_margin_bottom = 5.0
|
||||
draw_center = false
|
||||
|
||||
[node name="ExampleBalloon" type="CanvasLayer"]
|
||||
layer = 100
|
||||
script = ExtResource("1_4u26j")
|
||||
|
||||
[node name="Balloon" type="ColorRect" parent="."]
|
||||
color = Color(0, 0, 0, 1)
|
||||
|
||||
[node name="Margin" type="MarginContainer" parent="Balloon"]
|
||||
layout_mode = 0
|
||||
anchor_right = 1.0
|
||||
offset_bottom = 119.0
|
||||
grow_horizontal = 2
|
||||
theme_override_constants/margin_left = 20
|
||||
theme_override_constants/margin_top = 10
|
||||
theme_override_constants/margin_right = 20
|
||||
theme_override_constants/margin_bottom = 10
|
||||
metadata/_edit_layout_mode = 1
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="Balloon/Margin"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 10
|
||||
|
||||
[node name="CharacterLabel" type="RichTextLabel" parent="Balloon/Margin/VBox"]
|
||||
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/Margin/VBox" instance=ExtResource("2_a8ve6")]
|
||||
layout_mode = 2
|
||||
text = "Dialogue"
|
||||
|
||||
[node name="Responses" type="VBoxContainer" parent="Balloon/Margin/VBox"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 2
|
||||
|
||||
[node name="ResponseTemplate" type="RichTextLabel" parent="Balloon/Margin/VBox"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_override_styles/focus = SubResource("StyleBoxFlat_5d24i")
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_oj3c8")
|
||||
bbcode_enabled = true
|
||||
text = "Response"
|
||||
fit_content = true
|
||||
scroll_active = false
|
||||
shortcut_keys_enabled = false
|
||||
meta_underlined = false
|
||||
hint_underlined = false
|
||||
|
||||
[connection signal="gui_input" from="Balloon" to="." method="_on_balloon_gui_input"]
|
||||
[connection signal="resized" from="Balloon/Margin" to="." method="_on_margin_resized"]
|
|
@ -0,0 +1,87 @@
|
|||
[gd_scene load_steps=8 format=3 uid="uid://b361p61jmf257"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/dialogue_manager/example_balloon/example_balloon.gd" id="1_4u26j"]
|
||||
[ext_resource type="PackedScene" uid="uid://ckvgyvclnwggo" path="res://addons/dialogue_manager/dialogue_label.tscn" id="2_a8ve6"]
|
||||
|
||||
[sub_resource type="Theme" id="Theme_isg48"]
|
||||
default_font_size = 9
|
||||
|
||||
[sub_resource type="Theme" id="Theme_owda0"]
|
||||
default_font_size = 9
|
||||
|
||||
[sub_resource type="Theme" id="Theme_fakos"]
|
||||
default_font_size = 9
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_5d24i"]
|
||||
content_margin_left = 20.0
|
||||
content_margin_top = 2.0
|
||||
content_margin_right = 2.0
|
||||
content_margin_bottom = 2.0
|
||||
bg_color = Color(1, 1, 1, 0.25098)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_oj3c8"]
|
||||
content_margin_left = 20.0
|
||||
content_margin_top = 2.0
|
||||
content_margin_right = 2.0
|
||||
content_margin_bottom = 2.0
|
||||
draw_center = false
|
||||
|
||||
[node name="ExampleBalloon" type="CanvasLayer"]
|
||||
layer = 100
|
||||
script = ExtResource("1_4u26j")
|
||||
|
||||
[node name="Balloon" type="ColorRect" parent="."]
|
||||
color = Color(0, 0, 0, 1)
|
||||
|
||||
[node name="Margin" type="MarginContainer" parent="Balloon"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 10
|
||||
anchor_right = 1.0
|
||||
offset_bottom = 75.0
|
||||
grow_horizontal = 2
|
||||
theme_override_constants/margin_left = 20
|
||||
theme_override_constants/margin_top = 10
|
||||
theme_override_constants/margin_right = 20
|
||||
theme_override_constants/margin_bottom = 10
|
||||
metadata/_edit_layout_mode = 1
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="Balloon/Margin"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 4
|
||||
|
||||
[node name="CharacterLabel" type="RichTextLabel" parent="Balloon/Margin/VBox"]
|
||||
modulate = Color(1, 1, 1, 0.501961)
|
||||
layout_mode = 2
|
||||
mouse_filter = 1
|
||||
theme = SubResource("Theme_isg48")
|
||||
bbcode_enabled = true
|
||||
text = "Character"
|
||||
fit_content = true
|
||||
scroll_active = false
|
||||
|
||||
[node name="DialogueLabel" parent="Balloon/Margin/VBox" instance=ExtResource("2_a8ve6")]
|
||||
layout_mode = 2
|
||||
theme = SubResource("Theme_owda0")
|
||||
text = "Dialogue"
|
||||
|
||||
[node name="Responses" type="VBoxContainer" parent="Balloon/Margin/VBox"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 1
|
||||
|
||||
[node name="ResponseTemplate" type="RichTextLabel" parent="Balloon/Margin/VBox"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
focus_mode = 2
|
||||
theme = SubResource("Theme_fakos")
|
||||
theme_override_styles/focus = SubResource("StyleBoxFlat_5d24i")
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_oj3c8")
|
||||
bbcode_enabled = true
|
||||
text = "Response"
|
||||
fit_content = true
|
||||
scroll_active = false
|
||||
shortcut_keys_enabled = false
|
||||
meta_underlined = false
|
||||
hint_underlined = false
|
||||
|
||||
[connection signal="gui_input" from="Balloon" to="." method="_on_balloon_gui_input"]
|
||||
[connection signal="resized" from="Balloon/Margin" to="." method="_on_margin_resized"]
|
|
@ -0,0 +1,109 @@
|
|||
@tool
|
||||
extends EditorImportPlugin
|
||||
|
||||
|
||||
signal compiled_resource(resource: Resource)
|
||||
|
||||
|
||||
const DialogueResource = preload("res://addons/dialogue_manager/dialogue_resource.gd")
|
||||
const compiler_version = 8
|
||||
|
||||
|
||||
var editor_plugin
|
||||
|
||||
|
||||
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:
|
||||
return compile_file(source_file, "%s.%s" % [save_path, _get_save_extension()])
|
||||
|
||||
|
||||
func compile_file(path: String, resource_path: String, will_cascade_cache_data: bool = true) -> Error:
|
||||
# Get the raw file contents
|
||||
if not FileAccess.file_exists(path): return ERR_FILE_NOT_FOUND
|
||||
|
||||
var file: FileAccess = FileAccess.open(path, FileAccess.READ)
|
||||
var raw_text: String = file.get_as_text()
|
||||
|
||||
# Parse the text
|
||||
var parser: DialogueManagerParser = DialogueManagerParser.new()
|
||||
var err: Error = parser.parse(raw_text, path)
|
||||
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(), path])
|
||||
editor_plugin.add_errors_to_dialogue_file_cache(path, 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.titles = data.titles
|
||||
resource.first_title = data.first_title
|
||||
resource.character_names = data.character_names
|
||||
resource.lines = data.lines
|
||||
|
||||
if will_cascade_cache_data:
|
||||
editor_plugin.add_to_dialogue_file_cache(path, resource_path, data)
|
||||
|
||||
err = ResourceSaver.save(resource, resource_path)
|
||||
|
||||
compiled_resource.emit(resource)
|
||||
|
||||
return err
|
Binary file not shown.
|
@ -0,0 +1,400 @@
|
|||
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 "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 "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.revert_to_default_test_scene"
|
||||
msgstr "Revert to default test scene"
|
||||
|
||||
msgid "settings.autoload"
|
||||
msgstr "Autload"
|
||||
|
||||
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.wrap_long_lines"
|
||||
msgstr "Wrap long lines"
|
||||
|
||||
msgid "settings.include_failed_responses"
|
||||
msgstr "Include responses with failed conditions"
|
||||
|
||||
msgid "settings.custom_test_scene"
|
||||
msgstr "Custom test scene (must extend BaseDialogueTestScene)"
|
||||
|
||||
msgid "settings.states_shortcuts"
|
||||
msgstr "State Shortcuts"
|
||||
|
||||
msgid "settings.states_message"
|
||||
msgstr "If an autoload is enabled here you can refer to its properties and methods without having to use its name."
|
||||
|
||||
msgid "settings.states_hint"
|
||||
msgstr "ie. Instead of \"SomeState.some_property\" you could just use \"some_property\""
|
||||
|
||||
msgid "n_of_n"
|
||||
msgstr "{index} of {total}"
|
||||
|
||||
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.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.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 nested."
|
||||
|
||||
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 "Incomplate 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.unsupported_array_method"
|
||||
msgstr "Calling \"{method_name}\" on an array isn't supported."
|
||||
|
||||
msgid "runtime.unsupported_dictionary_method"
|
||||
msgstr "Calling \"{method_name}\" on a dictionary isn't supported."
|
||||
|
||||
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}\" is not a property on any game states ({states})."
|
||||
|
||||
msgid "runtime.method_not_found"
|
||||
msgstr "\"{method}\" is not a method on any game states ({states})"
|
||||
|
||||
msgid "runtime.signal_not_found"
|
||||
msgstr "\"{signal_name}\" is not a signal on any game states ({states})"
|
||||
|
||||
msgid "runtime.method_not_callable"
|
||||
msgstr "\"{method}\" is not a callable method on \"{object}\""
|
||||
|
||||
msgid "runtime.unknown_operator"
|
||||
msgstr "Unknown operator."
|
||||
|
||||
msgid "runtime.something_went_wrong"
|
||||
msgstr "Something went wrong."
|
|
@ -0,0 +1,390 @@
|
|||
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 "test_dialogue"
|
||||
msgstr ""
|
||||
|
||||
msgid "search_for_text"
|
||||
msgstr ""
|
||||
|
||||
msgid "insert"
|
||||
msgstr ""
|
||||
|
||||
msgid "translations"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings"
|
||||
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.revert_to_default_test_scene"
|
||||
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.wrap_long_lines"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings.include_failed_responses"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings.custom_test_scene"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings.states_shortcuts"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings.states_message"
|
||||
msgstr ""
|
||||
|
||||
msgid "settings.states_hint"
|
||||
msgstr ""
|
||||
|
||||
msgid "n_of_n"
|
||||
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.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.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.unsupported_array_method"
|
||||
msgstr ""
|
||||
|
||||
msgid "runtime.unsupported_dictionary_method"
|
||||
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.method_not_found"
|
||||
msgstr ""
|
||||
|
||||
msgid "runtime.signal_not_found"
|
||||
msgstr ""
|
||||
|
||||
msgid "runtime.method_not_callable"
|
||||
msgstr ""
|
||||
|
||||
msgid "runtime.unknown_operator"
|
||||
msgstr ""
|
||||
|
||||
msgid "runtime.something_went_wrong"
|
||||
msgstr ""
|
|
@ -0,0 +1,7 @@
|
|||
[plugin]
|
||||
|
||||
name="Dialogue Manager"
|
||||
description="A simple but powerful branching dialogue system"
|
||||
author="Nathan Hoad"
|
||||
version="2.16.2"
|
||||
script="plugin.gd"
|
|
@ -0,0 +1,302 @@
|
|||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
|
||||
const DialogueConstants = preload("res://addons/dialogue_manager/constants.gd")
|
||||
const DialogueImportPlugin = preload("res://addons/dialogue_manager/import_plugin.gd")
|
||||
const DialogueTranslationParserPlugin = preload("res://addons/dialogue_manager/editor_translation_parser_plugin.gd")
|
||||
const DialogueSettings = preload("res://addons/dialogue_manager/components/settings.gd")
|
||||
const MainView = preload("res://addons/dialogue_manager/views/main_view.tscn")
|
||||
|
||||
|
||||
var import_plugin: DialogueImportPlugin
|
||||
var translation_parser_plugin: DialogueTranslationParserPlugin
|
||||
var main_view
|
||||
|
||||
var dialogue_file_cache: Dictionary = {}
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
add_autoload_singleton("DialogueManager", "res://addons/dialogue_manager/dialogue_manager.gd")
|
||||
add_custom_type("DialogueLabel", "RichTextLabel", preload("res://addons/dialogue_manager/dialogue_label.gd"), _get_plugin_icon())
|
||||
|
||||
if Engine.is_editor_hint():
|
||||
DialogueSettings.prepare()
|
||||
|
||||
import_plugin = DialogueImportPlugin.new()
|
||||
import_plugin.editor_plugin = self
|
||||
add_import_plugin(import_plugin)
|
||||
|
||||
translation_parser_plugin = DialogueTranslationParserPlugin.new()
|
||||
add_translation_parser_plugin(translation_parser_plugin)
|
||||
|
||||
main_view = MainView.instantiate()
|
||||
main_view.editor_plugin = self
|
||||
get_editor_interface().get_editor_main_screen().add_child(main_view)
|
||||
_make_visible(false)
|
||||
|
||||
update_dialogue_file_cache()
|
||||
get_editor_interface().get_resource_filesystem().filesystem_changed.connect(_on_filesystem_changed)
|
||||
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)
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
remove_autoload_singleton("DialogueManager")
|
||||
remove_custom_type("DialogueLabel")
|
||||
|
||||
remove_import_plugin(import_plugin)
|
||||
import_plugin = null
|
||||
|
||||
remove_translation_parser_plugin(translation_parser_plugin)
|
||||
translation_parser_plugin = null
|
||||
|
||||
if is_instance_valid(main_view):
|
||||
main_view.queue_free()
|
||||
|
||||
get_editor_interface().get_resource_filesystem().filesystem_changed.disconnect(_on_filesystem_changed)
|
||||
get_editor_interface().get_file_system_dock().files_moved.disconnect(_on_files_moved)
|
||||
|
||||
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("res://addons/dialogue_manager/assets/icon.svg")
|
||||
|
||||
|
||||
func _handles(object) -> bool:
|
||||
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()
|
||||
|
||||
|
||||
func _build() -> bool:
|
||||
# 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
|
||||
|
||||
var can_build: bool = true
|
||||
var is_first_file: bool = true
|
||||
for dialogue_file in dialogue_file_cache.values():
|
||||
if dialogue_file.errors.size() > 0:
|
||||
# Open the first file
|
||||
if is_first_file:
|
||||
get_editor_interface().edit_resource(load(dialogue_file.path))
|
||||
main_view.show_build_error_dialog()
|
||||
is_first_file = false
|
||||
push_error("You have %d error(s) in %s" % [dialogue_file.errors.size(), dialogue_file.path])
|
||||
can_build = false
|
||||
return can_build
|
||||
|
||||
|
||||
## Keep track of known files and their dependencies
|
||||
func add_to_dialogue_file_cache(path: String, resource_path: String, parse_results: DialogueManagerParseResult) -> void:
|
||||
dialogue_file_cache[path] = {
|
||||
path = path,
|
||||
resource_path = resource_path,
|
||||
dependencies = Array(parse_results.imported_paths).filter(func(d): return d != path),
|
||||
errors = []
|
||||
}
|
||||
|
||||
save_dialogue_cache()
|
||||
recompile_dependent_files(path)
|
||||
|
||||
|
||||
## Keep track of compile errors
|
||||
func add_errors_to_dialogue_file_cache(path: String, errors: Array[Dictionary]) -> void:
|
||||
if dialogue_file_cache.has(path):
|
||||
dialogue_file_cache[path]["errors"] = errors
|
||||
else:
|
||||
dialogue_file_cache[path] = {
|
||||
path = path,
|
||||
errors = errors
|
||||
}
|
||||
|
||||
save_dialogue_cache()
|
||||
recompile_dependent_files(path)
|
||||
|
||||
|
||||
## Update references to a moved file
|
||||
func update_import_paths(from_path: String, to_path: String) -> void:
|
||||
# Update its own reference in the cache
|
||||
if dialogue_file_cache.has(from_path):
|
||||
dialogue_file_cache[to_path] = dialogue_file_cache[from_path].duplicate()
|
||||
dialogue_file_cache.erase(from_path)
|
||||
|
||||
# Reopen the file if it's already open
|
||||
if main_view.current_file_path == from_path:
|
||||
main_view.current_file_path = ""
|
||||
main_view.open_file(to_path)
|
||||
|
||||
# Update any other files that import the moved file
|
||||
var dependents = dialogue_file_cache.values().filter(func(d): return from_path in d.dependencies)
|
||||
for dependent in dependents:
|
||||
dependent.dependencies.erase(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 = FileAccess.open(dependent.path, FileAccess.WRITE)
|
||||
file.store_string(text)
|
||||
|
||||
save_dialogue_cache()
|
||||
|
||||
|
||||
## Rebuild any files that depend on this path
|
||||
func recompile_dependent_files(path: String) -> void:
|
||||
# Rebuild any files that depend on this one
|
||||
var dependents = dialogue_file_cache.values().filter(func(d): return path in d.dependencies)
|
||||
for dependent in dependents:
|
||||
if dependent.has("path") and dependent.has("resource_path"):
|
||||
import_plugin.compile_file(dependent.path, dependent.resource_path, false)
|
||||
|
||||
|
||||
## Make sure the cache points to real files
|
||||
func update_dialogue_file_cache() -> void:
|
||||
var cache: Dictionary = {}
|
||||
|
||||
# Open our cache file if it exists
|
||||
if FileAccess.file_exists(DialogueConstants.CACHE_PATH):
|
||||
var file: FileAccess = FileAccess.open(DialogueConstants.CACHE_PATH, FileAccess.READ)
|
||||
cache = JSON.parse_string(file.get_as_text())
|
||||
|
||||
# Scan for dialogue files
|
||||
var current_files: PackedStringArray = _get_dialogue_files_in_filesystem()
|
||||
|
||||
# Add any 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 current_files:
|
||||
if not files_for_pot.has(path):
|
||||
files_for_pot.append(path)
|
||||
files_for_pot_changed = true
|
||||
|
||||
# Remove any files that don't exist any more
|
||||
for path in cache.keys():
|
||||
if not path in current_files:
|
||||
cache.erase(path)
|
||||
DialogueSettings.remove_recent_file(path)
|
||||
|
||||
# Remove missing files from POT generation
|
||||
if files_for_pot.has(path):
|
||||
files_for_pot.remove_at(files_for_pot.find(path))
|
||||
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()
|
||||
|
||||
dialogue_file_cache = cache
|
||||
|
||||
|
||||
## Persist the cache
|
||||
func save_dialogue_cache() -> void:
|
||||
var file: FileAccess = FileAccess.open(DialogueConstants.CACHE_PATH, FileAccess.WRITE)
|
||||
file.store_string(JSON.stringify(dialogue_file_cache))
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
|
||||
### 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 file: FileAccess = FileAccess.open("res://addons/dialogue_manager/example_balloon/example_balloon.tscn", FileAccess.READ)
|
||||
var file_contents: String = file.get_as_text().replace("res://addons/dialogue_manager/example_balloon/example_balloon.gd", path + "/balloon.gd")
|
||||
file = FileAccess.open(path + "/balloon.tscn", FileAccess.WRITE)
|
||||
file.store_string(file_contents)
|
||||
file.close()
|
||||
|
||||
file = FileAccess.open("res://addons/dialogue_manager/example_balloon/small_example_balloon.tscn", FileAccess.READ)
|
||||
file_contents = file.get_as_text().replace("res://addons/dialogue_manager/example_balloon/example_balloon.gd", path + "/balloon.gd")
|
||||
file = FileAccess.open(path + "/small_balloon.tscn", FileAccess.WRITE)
|
||||
file.store_string(file_contents)
|
||||
file.close()
|
||||
|
||||
file = FileAccess.open("res://addons/dialogue_manager/example_balloon/example_balloon.gd", FileAccess.READ)
|
||||
file_contents = file.get_as_text()
|
||||
file = FileAccess.open(path + "/balloon.gd", 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", path + "/balloon.tscn")
|
||||
|
||||
directory_dialog.queue_free()
|
||||
)
|
||||
get_editor_interface().get_base_control().add_child(directory_dialog)
|
||||
directory_dialog.popup_centered()
|
||||
|
||||
|
||||
### Signals
|
||||
|
||||
|
||||
func _on_filesystem_changed() -> void:
|
||||
update_dialogue_file_cache()
|
||||
|
||||
|
||||
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:
|
||||
recompile_dependent_files(file)
|
||||
if is_instance_valid(main_view):
|
||||
main_view.close_file(file)
|
|
@ -0,0 +1,29 @@
|
|||
class_name BaseDialogueTestScene extends Node2D
|
||||
|
||||
|
||||
const DialogueSettings = preload("res://addons/dialogue_manager/components/settings.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)
|
||||
|
||||
DialogueManager.dialogue_ended.connect(_on_dialogue_ended)
|
||||
|
||||
DialogueManager.show_example_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()
|
|
@ -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")
|
|
@ -0,0 +1,986 @@
|
|||
@tool
|
||||
extends Control
|
||||
|
||||
|
||||
const DialogueConstants = preload("res://addons/dialogue_manager/constants.gd")
|
||||
const DialogueSettings = preload("res://addons/dialogue_manager/components/settings.gd")
|
||||
|
||||
const OPEN_OPEN = 100
|
||||
const OPEN_CLEAR = 101
|
||||
|
||||
const TRANSLATIONS_GENERATE_LINE_IDS = 100
|
||||
const TRANSLATIONS_SAVE_CHARACTERS_TO_CSV = 201
|
||||
const TRANSLATIONS_SAVE_TO_CSV = 202
|
||||
const TRANSLATIONS_IMPORT_FROM_CSV = 203
|
||||
|
||||
const ITEM_SAVE = 100
|
||||
const ITEM_SAVE_AS = 101
|
||||
const ITEM_CLOSE = 102
|
||||
const ITEM_CLOSE_ALL = 103
|
||||
const ITEM_CLOSE_OTHERS = 104
|
||||
const ITEM_COPY_PATH = 200
|
||||
const ITEM_SHOW_IN_FILESYSTEM = 201
|
||||
|
||||
enum TranslationSource {
|
||||
CharacterNames,
|
||||
Lines
|
||||
}
|
||||
|
||||
|
||||
@onready var parse_timer := $ParseTimer
|
||||
|
||||
# Dialogs
|
||||
@onready var new_dialog: FileDialog = $NewDialog
|
||||
@onready var save_dialog: FileDialog = $SaveDialog
|
||||
@onready var open_dialog: FileDialog = $OpenDialog
|
||||
@onready var export_dialog: FileDialog = $ExportDialog
|
||||
@onready var import_dialog: FileDialog = $ImportDialog
|
||||
@onready var errors_dialog: AcceptDialog = $ErrorsDialog
|
||||
@onready var settings_dialog: AcceptDialog = $SettingsDialog
|
||||
@onready var settings_view := $SettingsDialog/SettingsView
|
||||
@onready var build_error_dialog: AcceptDialog = $BuildErrorDialog
|
||||
@onready var close_confirmation_dialog: ConfirmationDialog = $CloseConfirmationDialog
|
||||
@onready var updated_dialog: AcceptDialog = $UpdatedDialog
|
||||
|
||||
# Toolbar
|
||||
@onready var new_button: Button = %NewButton
|
||||
@onready var open_button: MenuButton = %OpenButton
|
||||
@onready var save_all_button: Button = %SaveAllButton
|
||||
@onready var test_button: Button = %TestButton
|
||||
@onready var search_button: Button = %SearchButton
|
||||
@onready var insert_button: MenuButton = %InsertButton
|
||||
@onready var translations_button: MenuButton = %TranslationsButton
|
||||
@onready var settings_button: Button = %SettingsButton
|
||||
@onready var docs_button: Button = %DocsButton
|
||||
@onready var version_label: Label = %VersionLabel
|
||||
@onready var update_button: Button = %UpdateButton
|
||||
|
||||
@onready var search_and_replace := %SearchAndReplace
|
||||
|
||||
# Code editor
|
||||
@onready var content: HSplitContainer = %Content
|
||||
@onready var files_list := %FilesList
|
||||
@onready var files_popup_menu: PopupMenu = %FilesPopupMenu
|
||||
@onready var title_list := %TitleList
|
||||
@onready var code_edit := %CodeEdit
|
||||
@onready var errors_panel := %ErrorsPanel
|
||||
|
||||
# The Dialogue Manager plugin
|
||||
var editor_plugin: EditorPlugin
|
||||
|
||||
# The currently open file
|
||||
var current_file_path: String = "":
|
||||
set(next_current_file_path):
|
||||
current_file_path = next_current_file_path
|
||||
files_list.current_file_path = current_file_path
|
||||
if current_file_path == "":
|
||||
save_all_button.disabled = true
|
||||
test_button.disabled = true
|
||||
search_button.disabled = true
|
||||
insert_button.disabled = true
|
||||
translations_button.disabled = true
|
||||
content.dragger_visibility = SplitContainer.DRAGGER_HIDDEN
|
||||
files_list.hide()
|
||||
title_list.hide()
|
||||
code_edit.hide()
|
||||
else:
|
||||
test_button.disabled = false
|
||||
search_button.disabled = false
|
||||
insert_button.disabled = false
|
||||
translations_button.disabled = false
|
||||
content.dragger_visibility = SplitContainer.DRAGGER_VISIBLE
|
||||
files_list.show()
|
||||
title_list.show()
|
||||
code_edit.show()
|
||||
|
||||
code_edit.text = open_buffers[current_file_path].text
|
||||
code_edit.errors = []
|
||||
code_edit.clear_undo_history()
|
||||
code_edit.set_cursor(DialogueSettings.get_caret(current_file_path))
|
||||
code_edit.grab_focus()
|
||||
|
||||
_on_code_edit_text_changed()
|
||||
|
||||
errors_panel.errors = []
|
||||
code_edit.errors = []
|
||||
get:
|
||||
return current_file_path
|
||||
|
||||
# A reference to the currently open files and their last saved text
|
||||
var open_buffers: Dictionary = {}
|
||||
|
||||
# Which thing are we exporting translations for?
|
||||
var translation_source: TranslationSource = TranslationSource.Lines
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
apply_theme()
|
||||
|
||||
# Start with nothing open
|
||||
self.current_file_path = ""
|
||||
|
||||
# Set up the update checker
|
||||
version_label.text = "v%s" % update_button.get_version()
|
||||
update_button.editor_plugin = editor_plugin
|
||||
update_button.on_before_refresh = func on_before_refresh():
|
||||
# Save everything
|
||||
DialogueSettings.set_user_value("just_refreshed", {
|
||||
current_file_path = current_file_path,
|
||||
open_buffers = open_buffers
|
||||
})
|
||||
return true
|
||||
|
||||
# Did we just load from an addon version refresh?
|
||||
var just_refreshed = DialogueSettings.get_user_value("just_refreshed", null)
|
||||
if just_refreshed != null:
|
||||
DialogueSettings.set_user_value("just_refreshed", null)
|
||||
call_deferred("load_from_version_refresh", just_refreshed)
|
||||
|
||||
# Hook up the search toolbar
|
||||
search_and_replace.code_edit = code_edit
|
||||
|
||||
# Connect menu buttons
|
||||
insert_button.get_popup().id_pressed.connect(_on_insert_button_menu_id_pressed)
|
||||
translations_button.get_popup().id_pressed.connect(_on_translations_button_menu_id_pressed)
|
||||
|
||||
code_edit.main_view = self
|
||||
code_edit.wrap_mode = TextEdit.LINE_WRAPPING_BOUNDARY if DialogueSettings.get_setting("wrap_lines", false) else TextEdit.LINE_WRAPPING_NONE
|
||||
var editor_settings: EditorSettings = editor_plugin.get_editor_interface().get_editor_settings()
|
||||
editor_settings.settings_changed.connect(_on_editor_settings_changed)
|
||||
_on_editor_settings_changed()
|
||||
|
||||
save_all_button.disabled = true
|
||||
|
||||
close_confirmation_dialog.ok_button_text = DialogueConstants.translate("confirm_close.save")
|
||||
close_confirmation_dialog.add_button(DialogueConstants.translate("confirm_close.discard"), true, "discard")
|
||||
|
||||
settings_view.editor_plugin = editor_plugin
|
||||
|
||||
errors_dialog.dialog_text = DialogueConstants.translate("errors_in_script")
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if not visible: return
|
||||
|
||||
if event is InputEventKey and event.is_pressed():
|
||||
match event.as_text():
|
||||
"Ctrl+Alt+S":
|
||||
save_file(current_file_path)
|
||||
"Ctrl+W":
|
||||
get_viewport().set_input_as_handled()
|
||||
close_file(current_file_path)
|
||||
"Ctrl+F5":
|
||||
_on_test_button_pressed()
|
||||
|
||||
|
||||
func apply_changes() -> void:
|
||||
save_files()
|
||||
|
||||
|
||||
# Load back to the previous buffer regardless of if it was actually saved
|
||||
func load_from_version_refresh(just_refreshed: Dictionary) -> void:
|
||||
if just_refreshed.has("current_file_content"):
|
||||
# We just loaded from a version before multiple buffers
|
||||
var file: FileAccess = FileAccess.open(just_refreshed.current_file_path, FileAccess.READ)
|
||||
var file_text: String = file.get_as_text()
|
||||
open_buffers[just_refreshed.current_file_path] = {
|
||||
pristine_text = file_text,
|
||||
text = just_refreshed.current_file_content
|
||||
}
|
||||
else:
|
||||
open_buffers = just_refreshed.open_buffers
|
||||
|
||||
if just_refreshed.current_file_path != "":
|
||||
editor_plugin.get_editor_interface().edit_resource(load(just_refreshed.current_file_path))
|
||||
else:
|
||||
editor_plugin.get_editor_interface().set_main_screen_editor("Dialogue")
|
||||
|
||||
updated_dialog.dialog_text = DialogueConstants.translate("update.success").format({ version = update_button.get_version() })
|
||||
updated_dialog.popup_centered()
|
||||
|
||||
|
||||
func new_file(path: String, content: String = "") -> void:
|
||||
if open_buffers.has(path):
|
||||
remove_file_from_open_buffers(path)
|
||||
|
||||
var file: FileAccess = FileAccess.open(path, FileAccess.WRITE)
|
||||
if content == "":
|
||||
if DialogueSettings.get_setting("new_with_template", true):
|
||||
file.store_string("\n".join([
|
||||
"~ this_is_a_node_title",
|
||||
"",
|
||||
"Nathan: [[Hi|Hello|Howdy]], this is some dialogue.",
|
||||
"Nathan: Here are some choices.",
|
||||
"- First one",
|
||||
"\tNathan: You picked the first one.",
|
||||
"- Second one",
|
||||
"\tNathan: 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"
|
||||
]))
|
||||
else:
|
||||
file.store_string(content)
|
||||
|
||||
editor_plugin.get_editor_interface().get_resource_filesystem().scan()
|
||||
|
||||
|
||||
# Open a dialogue resource for editing
|
||||
func open_resource(resource: DialogueResource) -> void:
|
||||
open_file(resource.resource_path)
|
||||
|
||||
|
||||
func open_file(path: String) -> void:
|
||||
if not open_buffers.has(path):
|
||||
var file: FileAccess = FileAccess.open(path, FileAccess.READ)
|
||||
var text = file.get_as_text()
|
||||
|
||||
open_buffers[path] = {
|
||||
cursor = Vector2.ZERO,
|
||||
text = text,
|
||||
pristine_text = text
|
||||
}
|
||||
|
||||
DialogueSettings.add_recent_file(path)
|
||||
build_open_menu()
|
||||
|
||||
files_list.files = open_buffers.keys()
|
||||
files_list.select_file(path)
|
||||
|
||||
self.current_file_path = path
|
||||
|
||||
|
||||
func show_file_in_filesystem(path: String) -> void:
|
||||
var file_system = editor_plugin.get_editor_interface().get_file_system_dock()
|
||||
file_system.navigate_to_path(path)
|
||||
|
||||
|
||||
# Save any open files
|
||||
func save_files() -> void:
|
||||
var saved_files: PackedStringArray = []
|
||||
for path in open_buffers:
|
||||
if open_buffers[path].text != open_buffers[path].pristine_text:
|
||||
saved_files.append(path)
|
||||
save_file(path)
|
||||
|
||||
# Make sure we reimport/recompile the changes
|
||||
if saved_files.size() > 0:
|
||||
editor_plugin.get_editor_interface().get_resource_filesystem().reimport_files(saved_files)
|
||||
save_all_button.disabled = true
|
||||
|
||||
|
||||
# Save a file
|
||||
func save_file(path: String) -> void:
|
||||
var buffer = open_buffers[path]
|
||||
|
||||
files_list.mark_file_as_unsaved(path, false)
|
||||
save_all_button.disabled = files_list.unsaved_files.size() == 0
|
||||
|
||||
# Don't bother saving if there is nothing to save
|
||||
if buffer.text == buffer.pristine_text:
|
||||
return
|
||||
|
||||
buffer.pristine_text = buffer.text
|
||||
|
||||
# Save the current text
|
||||
var file: FileAccess = FileAccess.open(path, FileAccess.WRITE)
|
||||
file.store_string(buffer.text)
|
||||
file.close()
|
||||
|
||||
|
||||
func close_file(file: String) -> void:
|
||||
if not file in open_buffers.keys(): return
|
||||
|
||||
var buffer = open_buffers[file]
|
||||
|
||||
if buffer.text == buffer.pristine_text:
|
||||
remove_file_from_open_buffers(file)
|
||||
else:
|
||||
close_confirmation_dialog.dialog_text = DialogueConstants.translate("confirm_close").format({ path = file.get_file() })
|
||||
close_confirmation_dialog.popup_centered()
|
||||
|
||||
|
||||
func remove_file_from_open_buffers(file: String) -> void:
|
||||
if not file in open_buffers.keys(): return
|
||||
|
||||
var current_index = open_buffers.keys().find(file)
|
||||
|
||||
open_buffers.erase(file)
|
||||
if open_buffers.size() == 0:
|
||||
self.current_file_path = ""
|
||||
else:
|
||||
current_index = clamp(current_index, 0, open_buffers.size() - 1)
|
||||
self.current_file_path = open_buffers.keys()[current_index]
|
||||
files_list.files = open_buffers.keys()
|
||||
|
||||
|
||||
# Apply theme colors and icons to the UI
|
||||
func apply_theme() -> void:
|
||||
if is_instance_valid(editor_plugin) and is_instance_valid(code_edit):
|
||||
var scale: float = editor_plugin.get_editor_interface().get_editor_scale()
|
||||
var editor_settings = editor_plugin.get_editor_interface().get_editor_settings()
|
||||
code_edit.theme_overrides = {
|
||||
scale = scale,
|
||||
|
||||
background_color = editor_settings.get_setting("text_editor/theme/highlighting/background_color"),
|
||||
current_line_color = editor_settings.get_setting("text_editor/theme/highlighting/current_line_color"),
|
||||
error_line_color = editor_settings.get_setting("text_editor/theme/highlighting/mark_color"),
|
||||
|
||||
titles_color = editor_settings.get_setting("text_editor/theme/highlighting/control_flow_keyword_color"),
|
||||
text_color = editor_settings.get_setting("text_editor/theme/highlighting/text_color"),
|
||||
conditions_color = editor_settings.get_setting("text_editor/theme/highlighting/keyword_color"),
|
||||
mutations_color = editor_settings.get_setting("text_editor/theme/highlighting/function_color"),
|
||||
members_color = editor_settings.get_setting("text_editor/theme/highlighting/member_variable_color"),
|
||||
strings_color = editor_settings.get_setting("text_editor/theme/highlighting/string_color"),
|
||||
numbers_color = editor_settings.get_setting("text_editor/theme/highlighting/number_color"),
|
||||
symbols_color = editor_settings.get_setting("text_editor/theme/highlighting/symbol_color"),
|
||||
comments_color = editor_settings.get_setting("text_editor/theme/highlighting/comment_color"),
|
||||
jumps_color = Color(editor_settings.get_setting("text_editor/theme/highlighting/control_flow_keyword_color"), 0.7),
|
||||
|
||||
font_size = editor_settings.get_setting("interface/editor/code_font_size")
|
||||
}
|
||||
|
||||
new_button.icon = get_theme_icon("New", "EditorIcons")
|
||||
new_button.tooltip_text = DialogueConstants.translate("start_a_new_file")
|
||||
|
||||
open_button.icon = get_theme_icon("Load", "EditorIcons")
|
||||
open_button.tooltip_text = DialogueConstants.translate("open_a_file")
|
||||
|
||||
save_all_button.icon = get_theme_icon("Save", "EditorIcons")
|
||||
save_all_button.tooltip_text = DialogueConstants.translate("start_all_files")
|
||||
|
||||
test_button.icon = get_theme_icon("PlayScene", "EditorIcons")
|
||||
test_button.tooltip_text = DialogueConstants.translate("test_dialogue")
|
||||
|
||||
search_button.icon = get_theme_icon("Search", "EditorIcons")
|
||||
search_button.tooltip_text = DialogueConstants.translate("search_for_text")
|
||||
|
||||
insert_button.icon = get_theme_icon("RichTextEffect", "EditorIcons")
|
||||
insert_button.text = DialogueConstants.translate("insert")
|
||||
|
||||
translations_button.icon = get_theme_icon("Translation", "EditorIcons")
|
||||
translations_button.text = DialogueConstants.translate("translations")
|
||||
|
||||
settings_button.icon = get_theme_icon("Tools", "EditorIcons")
|
||||
settings_button.tooltip_text = DialogueConstants.translate("settings")
|
||||
|
||||
docs_button.icon = get_theme_icon("Help", "EditorIcons")
|
||||
docs_button.text = DialogueConstants.translate("docs")
|
||||
|
||||
update_button.apply_theme()
|
||||
|
||||
# Set up the effect menu
|
||||
var popup: PopupMenu = insert_button.get_popup()
|
||||
popup.clear()
|
||||
popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DialogueConstants.translate("insert.wave_bbcode"), 0)
|
||||
popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DialogueConstants.translate("insert.shake_bbcode"), 1)
|
||||
popup.add_separator()
|
||||
popup.add_icon_item(get_theme_icon("Time", "EditorIcons"), DialogueConstants.translate("insert.typing_pause"), 3)
|
||||
popup.add_icon_item(get_theme_icon("ViewportSpeed", "EditorIcons"), DialogueConstants.translate("insert.typing_speed_change"), 4)
|
||||
popup.add_icon_item(get_theme_icon("DebugNext", "EditorIcons"), DialogueConstants.translate("insert.auto_advance"), 5)
|
||||
popup.add_separator(DialogueConstants.translate("insert.templates"))
|
||||
popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DialogueConstants.translate("insert.title"), 6)
|
||||
popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DialogueConstants.translate("insert.dialogue"), 7)
|
||||
popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DialogueConstants.translate("insert.response"), 8)
|
||||
popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DialogueConstants.translate("insert.random_lines"), 9)
|
||||
popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DialogueConstants.translate("insert.random_text"), 10)
|
||||
popup.add_separator(DialogueConstants.translate("insert.actions"))
|
||||
popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DialogueConstants.translate("insert.jump"), 11)
|
||||
popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DialogueConstants.translate("insert.end_dialogue"), 12)
|
||||
|
||||
# Set up the translations menu
|
||||
popup = translations_button.get_popup()
|
||||
popup.clear()
|
||||
popup.add_icon_item(get_theme_icon("Translation", "EditorIcons"), DialogueConstants.translate("generate_line_ids"), TRANSLATIONS_GENERATE_LINE_IDS)
|
||||
popup.add_separator()
|
||||
popup.add_icon_item(get_theme_icon("FileList", "EditorIcons"), DialogueConstants.translate("save_characters_to_csv"), TRANSLATIONS_SAVE_CHARACTERS_TO_CSV)
|
||||
popup.add_icon_item(get_theme_icon("FileList", "EditorIcons"), DialogueConstants.translate("save_to_csv"), TRANSLATIONS_SAVE_TO_CSV)
|
||||
popup.add_icon_item(get_theme_icon("AssetLib", "EditorIcons"), DialogueConstants.translate("import_from_csv"), TRANSLATIONS_IMPORT_FROM_CSV)
|
||||
|
||||
# Dialog sizes
|
||||
new_dialog.min_size = Vector2(600, 500) * scale
|
||||
save_dialog.min_size = Vector2(600, 500) * scale
|
||||
open_dialog.min_size = Vector2(600, 500) * scale
|
||||
export_dialog.min_size = Vector2(600, 500) * scale
|
||||
export_dialog.min_size = Vector2(600, 500) * scale
|
||||
settings_dialog.min_size = Vector2(600, 600) * scale
|
||||
|
||||
|
||||
### Helpers
|
||||
|
||||
|
||||
# Refresh the open menu with the latest files
|
||||
func build_open_menu() -> void:
|
||||
var menu = open_button.get_popup()
|
||||
menu.clear()
|
||||
menu.add_icon_item(get_theme_icon("Load", "EditorIcons"), DialogueConstants.translate("open.open"), OPEN_OPEN)
|
||||
menu.add_separator()
|
||||
|
||||
var recent_files = DialogueSettings.get_recent_files()
|
||||
if recent_files.size() == 0:
|
||||
menu.add_item(DialogueConstants.translate("open.no_recent_files"))
|
||||
menu.set_item_disabled(2, true)
|
||||
else:
|
||||
for path in recent_files:
|
||||
menu.add_icon_item(get_theme_icon("File", "EditorIcons"), path)
|
||||
|
||||
menu.add_separator()
|
||||
menu.add_item(DialogueConstants.translate("open.clear_recent_files"), OPEN_CLEAR)
|
||||
if menu.id_pressed.is_connected(_on_open_menu_id_pressed):
|
||||
menu.id_pressed.disconnect(_on_open_menu_id_pressed)
|
||||
menu.id_pressed.connect(_on_open_menu_id_pressed)
|
||||
|
||||
|
||||
# Get the last place a CSV, etc was exported
|
||||
func get_last_export_path(extension: String) -> String:
|
||||
var filename = current_file_path.get_file().replace(".dialogue", "." + extension)
|
||||
return DialogueSettings.get_user_value("last_export_path", current_file_path.get_base_dir()) + "/" + filename
|
||||
|
||||
|
||||
# Check the current text for errors
|
||||
func parse() -> void:
|
||||
# Skip if nothing to parse
|
||||
if current_file_path == "": return
|
||||
|
||||
var parser = DialogueManagerParser.new()
|
||||
var errors: Array[Dictionary] = []
|
||||
if parser.parse(code_edit.text, current_file_path) != OK:
|
||||
errors = parser.get_errors()
|
||||
code_edit.errors = errors
|
||||
errors_panel.errors = errors
|
||||
parser.free()
|
||||
|
||||
|
||||
func show_build_error_dialog() -> void:
|
||||
build_error_dialog.dialog_text = DialogueConstants.translate("errors_with_build")
|
||||
build_error_dialog.popup_centered()
|
||||
|
||||
|
||||
# Generate translation line IDs for any line that doesn't already have one
|
||||
func generate_translations_keys() -> void:
|
||||
randomize()
|
||||
seed(Time.get_unix_time_from_system())
|
||||
|
||||
var parser = DialogueManagerParser.new()
|
||||
|
||||
var cursor: Vector2 = code_edit.get_cursor()
|
||||
var lines: PackedStringArray = code_edit.text.split("\n")
|
||||
|
||||
var key_regex = RegEx.new()
|
||||
key_regex.compile("\\[ID:(?<key>.*?)\\]")
|
||||
|
||||
# Make list of known keys
|
||||
var known_keys = {}
|
||||
for i in range(0, lines.size()):
|
||||
var line = lines[i]
|
||||
var found = key_regex.search(line)
|
||||
if found:
|
||||
var text = ""
|
||||
var l = line.replace(found.strings[0], "").strip_edges().strip_edges()
|
||||
if l.begins_with("- "):
|
||||
text = parser.extract_response_prompt(l)
|
||||
elif ":" in l:
|
||||
text = l.split(":")[1]
|
||||
else:
|
||||
text = l
|
||||
known_keys[found.strings[found.names.get("key")]] = text
|
||||
|
||||
# Add in any that are missing
|
||||
for i in lines.size():
|
||||
var line = lines[i]
|
||||
var l = line.strip_edges()
|
||||
|
||||
if parser.is_line_empty(l): continue
|
||||
if parser.is_condition_line(l, true): continue
|
||||
if parser.is_title_line(l): continue
|
||||
if parser.is_mutation_line(l): continue
|
||||
if parser.is_goto_line(l): continue
|
||||
if parser.is_import_line(l): continue
|
||||
|
||||
if "[ID:" in line: continue
|
||||
|
||||
var key = "t" + str(randi() % 1000000).sha1_text().substr(0, 10)
|
||||
while key in known_keys:
|
||||
key = "t" + str(randi() % 1000000).sha1_text().substr(0, 10)
|
||||
|
||||
var text = ""
|
||||
if l.begins_with("- "):
|
||||
text = parser.extract_response_prompt(l)
|
||||
else:
|
||||
text = l.substr(l.find(":") + 1)
|
||||
|
||||
lines[i] = line.replace(text, text + " [ID:%s]" % key)
|
||||
known_keys[key] = text
|
||||
|
||||
code_edit.text = "\n".join(lines)
|
||||
code_edit.set_cursor(cursor)
|
||||
_on_code_edit_text_changed()
|
||||
|
||||
parser.free()
|
||||
|
||||
|
||||
# Add a translation file to the project settings
|
||||
func add_path_to_project_translations(path: String) -> void:
|
||||
var translations: PackedStringArray = ProjectSettings.get_setting("internationalization/locale/translations")
|
||||
if not path in translations:
|
||||
translations.append(path)
|
||||
ProjectSettings.save()
|
||||
|
||||
|
||||
# Export dialogue and responses to CSV
|
||||
func export_translations_to_csv(path: String) -> void:
|
||||
var file: FileAccess
|
||||
|
||||
# If the file exists, open it first and work out which keys are already in it
|
||||
var existing_csv = {}
|
||||
var commas = []
|
||||
if FileAccess.file_exists(path):
|
||||
file = FileAccess.open(path, FileAccess.READ)
|
||||
var is_first_line = true
|
||||
var line: Array
|
||||
while !file.eof_reached():
|
||||
line = file.get_csv_line()
|
||||
if is_first_line:
|
||||
is_first_line = false
|
||||
for i in range(2, line.size()):
|
||||
commas.append("")
|
||||
# Make sure the line isn't empty before adding it
|
||||
if line.size() > 0 and line[0].strip_edges() != "":
|
||||
existing_csv[line[0]] = line
|
||||
|
||||
# Start a new file
|
||||
file = FileAccess.open(path, FileAccess.WRITE)
|
||||
|
||||
if not file.file_exists(path):
|
||||
file.store_csv_line(["keys", "en"])
|
||||
|
||||
# Write our translations to file
|
||||
var known_keys: PackedStringArray = []
|
||||
|
||||
var dialogue: Dictionary = DialogueManagerParser.parse_string(code_edit.text, current_file_path).lines
|
||||
|
||||
# Make a list of stuff that needs to go into the file
|
||||
var lines_to_save = []
|
||||
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 existing_csv.has(line.translation_key):
|
||||
var existing_line = existing_csv.get(line.translation_key)
|
||||
existing_line[1] = line.text
|
||||
lines_to_save.append(existing_line)
|
||||
existing_csv.erase(line.translation_key)
|
||||
else:
|
||||
lines_to_save.append(PackedStringArray([line.translation_key, line.text] + commas))
|
||||
|
||||
# Store lines in the file, starting with anything that already exists that hasn't been touched
|
||||
for line in existing_csv.values():
|
||||
file.store_csv_line(line)
|
||||
for line in lines_to_save:
|
||||
file.store_csv_line(line)
|
||||
|
||||
file.close()
|
||||
|
||||
editor_plugin.get_editor_interface().get_resource_filesystem().scan()
|
||||
editor_plugin.get_editor_interface().get_file_system_dock().call_deferred("navigate_to_path", path)
|
||||
|
||||
# Add it to the project l10n settings if it's not already there
|
||||
var translation_path: String = path.replace(".csv", ".en.translation")
|
||||
call_deferred("add_path_to_project_translations", translation_path)
|
||||
|
||||
|
||||
func export_character_names_to_csv(path: String) -> void:
|
||||
var file: FileAccess
|
||||
|
||||
# If the file exists, open it first and work out which keys are already in it
|
||||
var existing_csv = {}
|
||||
var commas = []
|
||||
if FileAccess.file_exists(path):
|
||||
file = FileAccess.open(path, FileAccess.READ)
|
||||
var is_first_line = true
|
||||
var line: Array
|
||||
while !file.eof_reached():
|
||||
line = file.get_csv_line()
|
||||
if is_first_line:
|
||||
is_first_line = false
|
||||
for i in range(2, line.size()):
|
||||
commas.append("")
|
||||
# Make sure the line isn't empty before adding it
|
||||
if line.size() > 0 and line[0].strip_edges() != "":
|
||||
existing_csv[line[0]] = line
|
||||
|
||||
# Start a new file
|
||||
file = FileAccess.open(path, FileAccess.WRITE)
|
||||
|
||||
if not file.file_exists(path):
|
||||
file.store_csv_line(["keys", "en"])
|
||||
|
||||
# Write our translations to file
|
||||
var known_keys: PackedStringArray = []
|
||||
|
||||
var character_names: PackedStringArray = DialogueManagerParser.parse_string(code_edit.text, current_file_path).character_names
|
||||
|
||||
# Make a list of stuff that needs to go into the file
|
||||
var lines_to_save = []
|
||||
for character_name in character_names:
|
||||
if character_name in known_keys: continue
|
||||
|
||||
known_keys.append(character_name)
|
||||
|
||||
if existing_csv.has(character_name):
|
||||
var existing_line = existing_csv.get(character_name)
|
||||
existing_line[1] = character_name
|
||||
lines_to_save.append(existing_line)
|
||||
existing_csv.erase(character_name)
|
||||
else:
|
||||
lines_to_save.append(PackedStringArray([character_name, character_name] + commas))
|
||||
|
||||
# Store lines in the file, starting with anything that already exists that hasn't been touched
|
||||
for line in existing_csv.values():
|
||||
file.store_csv_line(line)
|
||||
for line in lines_to_save:
|
||||
file.store_csv_line(line)
|
||||
|
||||
file.close()
|
||||
|
||||
editor_plugin.get_editor_interface().get_resource_filesystem().scan()
|
||||
editor_plugin.get_editor_interface().get_file_system_dock().call_deferred("navigate_to_path", path)
|
||||
|
||||
# Add it to the project l10n settings if it's not already there
|
||||
var translation_path: String = path.replace(".csv", ".en.translation")
|
||||
call_deferred("add_path_to_project_translations", translation_path)
|
||||
|
||||
|
||||
# Import changes back from an exported CSV by matching translation keys
|
||||
func import_translations_from_csv(path: String) -> void:
|
||||
var cursor: Vector2 = code_edit.get_cursor()
|
||||
|
||||
if not FileAccess.file_exists(path): return
|
||||
|
||||
# Open the CSV file and build a dictionary of the known keys
|
||||
var keys: Dictionary = {}
|
||||
var file: FileAccess = FileAccess.open(path, FileAccess.READ)
|
||||
var csv_line: Array
|
||||
while !file.eof_reached():
|
||||
csv_line = file.get_csv_line()
|
||||
if csv_line.size() > 1:
|
||||
keys[csv_line[0]] = csv_line[1]
|
||||
|
||||
var parser: DialogueManagerParser = DialogueManagerParser.new()
|
||||
|
||||
# Now look over each line in the dialogue and replace the content for matched keys
|
||||
var lines: PackedStringArray = code_edit.text.split("\n")
|
||||
var start_index: int = 0
|
||||
var end_index: int = 0
|
||||
for i in range(0, lines.size()):
|
||||
var line: String = lines[i]
|
||||
var translation_key: String = parser.extract_translation(line)
|
||||
if keys.has(translation_key):
|
||||
if parser.is_dialogue_line(line):
|
||||
start_index = 0
|
||||
# See if we need to skip over a character name
|
||||
line = line.replace("\\:", "!ESCAPED_COLON!")
|
||||
if ": " in line:
|
||||
start_index = line.find(": ") + 2
|
||||
lines[i] = (line.substr(0, start_index) + keys.get(translation_key) + " [ID:" + translation_key + "]").replace("!ESCAPED_COLON!", ":")
|
||||
|
||||
elif parser.is_response_line(line):
|
||||
start_index = line.find("- ") + 2
|
||||
# See if we need to skip over a character name
|
||||
line = line.replace("\\:", "!ESCAPED_COLON!")
|
||||
if ": " in line:
|
||||
start_index = line.find(": ") + 2
|
||||
end_index = line.length()
|
||||
if " =>" in line:
|
||||
end_index = line.find(" =>")
|
||||
if " [if " in line:
|
||||
end_index = line.find(" [if ")
|
||||
lines[i] = (line.substr(0, start_index) + keys.get(translation_key) + " [ID:" + translation_key + "]" + line.substr(end_index)).replace("!ESCAPED_COLON!", ":")
|
||||
|
||||
code_edit.text = "\n".join(lines)
|
||||
code_edit.set_cursor(cursor)
|
||||
|
||||
parser.free()
|
||||
|
||||
|
||||
### Signals
|
||||
|
||||
|
||||
func _on_editor_settings_changed() -> void:
|
||||
var editor_settings: EditorSettings = editor_plugin.get_editor_interface().get_editor_settings()
|
||||
code_edit.minimap_draw = editor_settings.get_setting("text_editor/appearance/minimap/show_minimap")
|
||||
code_edit.minimap_width = editor_settings.get_setting("text_editor/appearance/minimap/minimap_width")
|
||||
|
||||
|
||||
func _on_open_menu_id_pressed(id: int) -> void:
|
||||
match id:
|
||||
OPEN_OPEN:
|
||||
open_dialog.popup_centered()
|
||||
OPEN_CLEAR:
|
||||
DialogueSettings.clear_recent_files()
|
||||
build_open_menu()
|
||||
_:
|
||||
var menu = open_button.get_popup()
|
||||
var item = menu.get_item_text(menu.get_item_index(id))
|
||||
open_file(item)
|
||||
|
||||
|
||||
func _on_files_list_file_selected(file_path: String) -> void:
|
||||
self.current_file_path = file_path
|
||||
|
||||
|
||||
func _on_insert_button_menu_id_pressed(id: int) -> void:
|
||||
match id:
|
||||
0:
|
||||
code_edit.insert_bbcode("[wave amp=25 freq=5]", "[/wave]")
|
||||
1:
|
||||
code_edit.insert_bbcode("[shake rate=20 level=10]", "[/shake]")
|
||||
3:
|
||||
code_edit.insert_bbcode("[wait=1]")
|
||||
4:
|
||||
code_edit.insert_bbcode("[speed=0.2]")
|
||||
5:
|
||||
code_edit.insert_bbcode("[next=auto]")
|
||||
6:
|
||||
code_edit.insert_text("~ title")
|
||||
7:
|
||||
code_edit.insert_text("Nathan: This is Some Dialogue")
|
||||
8:
|
||||
code_edit.insert_text("Nathan: Choose a Response...\n- Option 1\n\tNathan: You chose option 1\n- Option 2\n\tNathan: You chose option 2")
|
||||
9:
|
||||
code_edit.insert_text("% Nathan: This is random line 1.\n% Nathan: This is random line 2.\n%1 Nathan: This is weighted random line 3.")
|
||||
10:
|
||||
code_edit.insert_text("Nathan: [[Hi|Hello|Howdy]]")
|
||||
11:
|
||||
code_edit.insert_text("=> title")
|
||||
12:
|
||||
code_edit.insert_text("=> END")
|
||||
|
||||
|
||||
func _on_translations_button_menu_id_pressed(id: int) -> void:
|
||||
match id:
|
||||
TRANSLATIONS_GENERATE_LINE_IDS:
|
||||
generate_translations_keys()
|
||||
|
||||
TRANSLATIONS_SAVE_CHARACTERS_TO_CSV:
|
||||
translation_source = TranslationSource.CharacterNames
|
||||
export_dialog.filters = PackedStringArray(["*.csv ; Translation CSV"])
|
||||
export_dialog.current_path = get_last_export_path("csv")
|
||||
export_dialog.popup_centered()
|
||||
|
||||
TRANSLATIONS_SAVE_TO_CSV:
|
||||
translation_source = TranslationSource.Lines
|
||||
export_dialog.filters = PackedStringArray(["*.csv ; Translation CSV"])
|
||||
export_dialog.current_path = get_last_export_path("csv")
|
||||
export_dialog.popup_centered()
|
||||
|
||||
TRANSLATIONS_IMPORT_FROM_CSV:
|
||||
import_dialog.current_path = get_last_export_path("csv")
|
||||
import_dialog.popup_centered()
|
||||
|
||||
|
||||
func _on_export_dialog_file_selected(path: String) -> void:
|
||||
DialogueSettings.set_user_value("last_export_path", path.get_base_dir())
|
||||
match path.get_extension():
|
||||
"csv":
|
||||
match translation_source:
|
||||
TranslationSource.CharacterNames:
|
||||
export_character_names_to_csv(path)
|
||||
TranslationSource.Lines:
|
||||
export_translations_to_csv(path)
|
||||
|
||||
|
||||
func _on_import_dialog_file_selected(path: String) -> void:
|
||||
DialogueSettings.set_user_value("last_export_path", path.get_base_dir())
|
||||
import_translations_from_csv(path)
|
||||
|
||||
|
||||
func _on_main_view_theme_changed():
|
||||
apply_theme()
|
||||
|
||||
|
||||
func _on_main_view_visibility_changed() -> void:
|
||||
if visible and is_instance_valid(code_edit):
|
||||
code_edit.grab_focus()
|
||||
|
||||
|
||||
func _on_new_button_pressed() -> void:
|
||||
new_dialog.current_file = ""
|
||||
new_dialog.popup_centered()
|
||||
|
||||
|
||||
func _on_new_dialog_file_selected(path: String) -> void:
|
||||
new_file(path)
|
||||
open_file(path)
|
||||
|
||||
|
||||
func _on_save_dialog_file_selected(path: String) -> void:
|
||||
new_file(path, code_edit.text)
|
||||
open_file(path)
|
||||
|
||||
|
||||
func _on_open_button_about_to_popup() -> void:
|
||||
build_open_menu()
|
||||
|
||||
|
||||
func _on_open_dialog_file_selected(path: String) -> void:
|
||||
open_file(path)
|
||||
|
||||
|
||||
func _on_save_all_button_pressed() -> void:
|
||||
save_files()
|
||||
|
||||
|
||||
func _on_code_edit_text_changed() -> void:
|
||||
title_list.titles = code_edit.get_titles()
|
||||
|
||||
var buffer = open_buffers[current_file_path]
|
||||
buffer.text = code_edit.text
|
||||
files_list.mark_file_as_unsaved(current_file_path, buffer.text != buffer.pristine_text)
|
||||
save_all_button.disabled = open_buffers.values().filter(func(d): return d.text != d.pristine_text).size() == 0
|
||||
|
||||
parse_timer.start(1)
|
||||
|
||||
|
||||
func _on_code_edit_active_title_change(title: String) -> void:
|
||||
title_list.select_title(title)
|
||||
DialogueSettings.set_user_value("run_title", title)
|
||||
|
||||
|
||||
func _on_code_edit_caret_changed() -> void:
|
||||
DialogueSettings.set_caret(current_file_path, code_edit.get_cursor())
|
||||
|
||||
|
||||
func _on_code_edit_error_clicked(line_number: int) -> void:
|
||||
errors_panel.show_error_for_line_number(line_number)
|
||||
|
||||
|
||||
func _on_title_list_title_selected(title: String) -> void:
|
||||
code_edit.go_to_title(title)
|
||||
code_edit.grab_focus()
|
||||
|
||||
|
||||
func _on_parse_timer_timeout() -> void:
|
||||
parse_timer.stop()
|
||||
parse()
|
||||
|
||||
|
||||
func _on_errors_panel_error_pressed(line_number: int, column_number: int) -> void:
|
||||
code_edit.set_caret_line(line_number)
|
||||
code_edit.set_caret_column(column_number)
|
||||
code_edit.grab_focus()
|
||||
|
||||
|
||||
func _on_search_button_toggled(button_pressed: bool) -> void:
|
||||
if code_edit.last_selected_text:
|
||||
search_and_replace.input.text = code_edit.last_selected_text
|
||||
|
||||
search_and_replace.visible = button_pressed
|
||||
|
||||
|
||||
func _on_search_and_replace_open_requested() -> void:
|
||||
search_button.set_pressed_no_signal(true)
|
||||
search_and_replace.visible = true
|
||||
|
||||
|
||||
func _on_search_and_replace_close_requested() -> void:
|
||||
search_button.set_pressed_no_signal(false)
|
||||
search_and_replace.visible = false
|
||||
code_edit.grab_focus()
|
||||
|
||||
|
||||
func _on_settings_button_pressed() -> void:
|
||||
settings_dialog.popup_centered()
|
||||
|
||||
|
||||
func _on_settings_view_script_button_pressed(path: String) -> void:
|
||||
settings_dialog.hide()
|
||||
editor_plugin.get_editor_interface().edit_resource(load(path))
|
||||
|
||||
|
||||
func _on_test_button_pressed() -> void:
|
||||
apply_changes()
|
||||
|
||||
if errors_panel.errors.size() > 0:
|
||||
errors_dialog.popup_centered()
|
||||
return
|
||||
|
||||
DialogueSettings.set_user_value("is_running_test_scene", true)
|
||||
DialogueSettings.set_user_value("run_resource_path", current_file_path)
|
||||
var test_scene_path: String = DialogueSettings.get_setting("custom_test_scene_path", "res://addons/dialogue_manager/test_scene.tscn")
|
||||
editor_plugin.get_editor_interface().play_custom_scene(test_scene_path)
|
||||
|
||||
|
||||
func _on_settings_dialog_confirmed() -> void:
|
||||
parse()
|
||||
code_edit.wrap_mode = TextEdit.LINE_WRAPPING_BOUNDARY if DialogueSettings.get_setting("wrap_lines", false) else TextEdit.LINE_WRAPPING_NONE
|
||||
code_edit.grab_focus()
|
||||
|
||||
|
||||
func _on_docs_button_pressed() -> void:
|
||||
OS.shell_open("https://github.com/nathanhoad/godot_dialogue_manager")
|
||||
|
||||
|
||||
func _on_files_list_file_popup_menu_requested(at_position: Vector2) -> void:
|
||||
files_popup_menu.position = Vector2(get_viewport().position) + files_list.global_position + at_position
|
||||
files_popup_menu.popup()
|
||||
|
||||
|
||||
func _on_files_popup_menu_about_to_popup() -> void:
|
||||
files_popup_menu.clear()
|
||||
|
||||
files_popup_menu.add_item(DialogueConstants.translate("buffer.save"), ITEM_SAVE, KEY_MASK_CTRL | KEY_MASK_ALT | KEY_S)
|
||||
files_popup_menu.add_item(DialogueConstants.translate("buffer.save_as"), ITEM_SAVE_AS)
|
||||
files_popup_menu.add_item(DialogueConstants.translate("buffer.close"), ITEM_CLOSE, KEY_MASK_CTRL | KEY_W)
|
||||
files_popup_menu.add_item(DialogueConstants.translate("buffer.close_all"), ITEM_CLOSE_ALL)
|
||||
files_popup_menu.add_item(DialogueConstants.translate("buffer.close_other_files"), ITEM_CLOSE_OTHERS)
|
||||
files_popup_menu.add_separator()
|
||||
files_popup_menu.add_item(DialogueConstants.translate("buffer.copy_file_path"), ITEM_COPY_PATH)
|
||||
files_popup_menu.add_item(DialogueConstants.translate("buffer.show_in_filesystem"), ITEM_SHOW_IN_FILESYSTEM)
|
||||
|
||||
|
||||
func _on_files_popup_menu_id_pressed(id: int) -> void:
|
||||
match id:
|
||||
ITEM_SAVE:
|
||||
save_file(current_file_path)
|
||||
ITEM_SAVE_AS:
|
||||
save_dialog.popup_centered()
|
||||
ITEM_CLOSE:
|
||||
close_file(current_file_path)
|
||||
ITEM_CLOSE_ALL:
|
||||
for path in open_buffers.keys():
|
||||
close_file(path)
|
||||
ITEM_CLOSE_OTHERS:
|
||||
for path in open_buffers.keys():
|
||||
if path != current_file_path:
|
||||
close_file(path)
|
||||
|
||||
ITEM_COPY_PATH:
|
||||
DisplayServer.clipboard_set(current_file_path)
|
||||
ITEM_SHOW_IN_FILESYSTEM:
|
||||
show_file_in_filesystem(current_file_path)
|
||||
|
||||
|
||||
func _on_code_edit_external_file_requested(path: String, title: String) -> void:
|
||||
open_file(path)
|
||||
if title != "":
|
||||
code_edit.go_to_title(title)
|
||||
else:
|
||||
code_edit.set_caret_line(0)
|
||||
|
||||
|
||||
func _on_close_confirmation_dialog_confirmed() -> void:
|
||||
save_file(current_file_path)
|
||||
remove_file_from_open_buffers(current_file_path)
|
||||
|
||||
|
||||
func _on_close_confirmation_dialog_custom_action(action: StringName) -> void:
|
||||
if action == "discard":
|
||||
remove_file_from_open_buffers(current_file_path)
|
||||
close_confirmation_dialog.hide()
|
|
@ -0,0 +1,348 @@
|
|||
[gd_scene load_steps=13 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="PackedScene" uid="uid://cpg4lg1r3ff6m" path="res://addons/dialogue_manager/views/settings_view.tscn" id="9_8bf36"]
|
||||
|
||||
[sub_resource type="Image" id="Image_0jq4a"]
|
||||
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_fguub"]
|
||||
image = SubResource("Image_0jq4a")
|
||||
|
||||
[sub_resource type="Image" id="Image_vtxr5"]
|
||||
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_wbkwf"]
|
||||
image = SubResource("Image_vtxr5")
|
||||
|
||||
[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
|
||||
dragger_visibility = 1
|
||||
|
||||
[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"
|
||||
icon = SubResource("ImageTexture_fguub")
|
||||
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"
|
||||
icon = SubResource("ImageTexture_fguub")
|
||||
item_count = 5
|
||||
popup/item_0/text = "Open..."
|
||||
popup/item_0/icon = SubResource("ImageTexture_wbkwf")
|
||||
popup/item_0/id = 0
|
||||
popup/item_1/text = ""
|
||||
popup/item_1/id = -1
|
||||
popup/item_1/separator = true
|
||||
popup/item_2/text = "res://examples/dialogue.dialogue"
|
||||
popup/item_2/icon = SubResource("ImageTexture_wbkwf")
|
||||
popup/item_2/id = 2
|
||||
popup/item_3/text = ""
|
||||
popup/item_3/id = -1
|
||||
popup/item_3/separator = true
|
||||
popup/item_4/text = "Clear recent files"
|
||||
popup/item_4/id = 4
|
||||
|
||||
[node name="SaveAllButton" type="Button" parent="Margin/Content/SidePanel/Toolbar"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
disabled = true
|
||||
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
|
||||
visible = false
|
||||
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
|
||||
visible = false
|
||||
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
|
||||
disabled = true
|
||||
text = "Insert"
|
||||
icon = SubResource("ImageTexture_fguub")
|
||||
item_count = 6
|
||||
popup/item_0/text = "Wave BBCode"
|
||||
popup/item_0/icon = SubResource("ImageTexture_fguub")
|
||||
popup/item_0/id = 0
|
||||
popup/item_1/text = "Shake BBCode"
|
||||
popup/item_1/icon = SubResource("ImageTexture_fguub")
|
||||
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_fguub")
|
||||
popup/item_3/id = 3
|
||||
popup/item_4/text = "Typing speed change"
|
||||
popup/item_4/icon = SubResource("ImageTexture_fguub")
|
||||
popup/item_4/id = 4
|
||||
popup/item_5/text = "Auto advance"
|
||||
popup/item_5/icon = SubResource("ImageTexture_fguub")
|
||||
popup/item_5/id = 5
|
||||
|
||||
[node name="TranslationsButton" type="MenuButton" parent="Margin/Content/CodePanel/Toolbar"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
disabled = true
|
||||
text = "Translations"
|
||||
icon = SubResource("ImageTexture_fguub")
|
||||
item_count = 6
|
||||
popup/item_0/text = "Generate line IDs"
|
||||
popup/item_0/icon = SubResource("ImageTexture_fguub")
|
||||
popup/item_0/id = 0
|
||||
popup/item_1/text = ""
|
||||
popup/item_1/id = -1
|
||||
popup/item_1/separator = true
|
||||
popup/item_2/text = "Save to CSV..."
|
||||
popup/item_2/icon = SubResource("ImageTexture_fguub")
|
||||
popup/item_2/id = 2
|
||||
popup/item_3/text = "Import changes from CSV..."
|
||||
popup/item_3/icon = SubResource("ImageTexture_fguub")
|
||||
popup/item_3/id = 3
|
||||
popup/item_4/text = ""
|
||||
popup/item_4/id = -1
|
||||
popup/item_4/separator = true
|
||||
popup/item_5/text = "Save to PO..."
|
||||
popup/item_5/icon = SubResource("ImageTexture_fguub")
|
||||
popup/item_5/id = 5
|
||||
|
||||
[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"
|
||||
disabled = true
|
||||
toggle_mode = true
|
||||
icon = SubResource("ImageTexture_fguub")
|
||||
flat = true
|
||||
|
||||
[node name="TestButton" type="Button" parent="Margin/Content/CodePanel/Toolbar"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
tooltip_text = "Test dialogue"
|
||||
disabled = true
|
||||
icon = SubResource("ImageTexture_fguub")
|
||||
flat = true
|
||||
|
||||
[node name="Spacer2" type="Control" parent="Margin/Content/CodePanel/Toolbar"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="SettingsButton" type="Button" parent="Margin/Content/CodePanel/Toolbar"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
tooltip_text = "Settings"
|
||||
icon = SubResource("ImageTexture_fguub")
|
||||
flat = true
|
||||
|
||||
[node name="Separator3" 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"
|
||||
icon = SubResource("ImageTexture_fguub")
|
||||
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.15.2"
|
||||
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
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
[node name="CodeEdit" parent="Margin/Content/CodePanel" instance=ExtResource("2_f73fm")]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
theme_override_colors/bookmark_color = Color(1, 0.333333, 0.333333, 1)
|
||||
text = ""
|
||||
|
||||
[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(600, 500)
|
||||
min_size = Vector2i(600, 500)
|
||||
dialog_hide_on_ok = true
|
||||
filters = PackedStringArray("*.dialogue ; Dialogue")
|
||||
|
||||
[node name="SaveDialog" type="FileDialog" parent="."]
|
||||
size = Vector2i(600, 500)
|
||||
min_size = Vector2i(600, 500)
|
||||
dialog_hide_on_ok = true
|
||||
filters = PackedStringArray("*.dialogue ; Dialogue")
|
||||
|
||||
[node name="OpenDialog" type="FileDialog" parent="."]
|
||||
title = "Open a File"
|
||||
size = Vector2i(600, 500)
|
||||
min_size = Vector2i(600, 500)
|
||||
ok_button_text = "Open"
|
||||
dialog_hide_on_ok = true
|
||||
file_mode = 0
|
||||
filters = PackedStringArray("*.dialogue ; Dialogue")
|
||||
|
||||
[node name="ExportDialog" type="FileDialog" parent="."]
|
||||
size = Vector2i(600, 500)
|
||||
min_size = Vector2i(600, 500)
|
||||
|
||||
[node name="ImportDialog" type="FileDialog" parent="."]
|
||||
size = Vector2i(600, 500)
|
||||
min_size = Vector2i(600, 500)
|
||||
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(834, 600)
|
||||
min_size = Vector2i(600, 600)
|
||||
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
|
||||
|
||||
[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!"
|
||||
|
||||
[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="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/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"]
|
|
@ -0,0 +1,144 @@
|
|||
@tool
|
||||
extends VBoxContainer
|
||||
|
||||
|
||||
signal script_button_pressed(path: String)
|
||||
|
||||
|
||||
const DialogueConstants = preload("res://addons/dialogue_manager/constants.gd")
|
||||
const DialogueSettings = preload("res://addons/dialogue_manager/components/settings.gd")
|
||||
|
||||
|
||||
const DEFAULT_TEST_SCENE_PATH = "res://addons/dialogue_manager/test_scene.tscn"
|
||||
|
||||
|
||||
@onready var new_template_button: CheckBox = $NewTemplateButton
|
||||
@onready var missing_translations_button: CheckBox = $MissingTranslationsButton
|
||||
@onready var wrap_lines_button: Button = $WrapLinesButton
|
||||
@onready var test_scene_path_input: LineEdit = $CustomTestScene/TestScenePath
|
||||
@onready var revert_test_scene_button: Button = $CustomTestScene/RevertTestScene
|
||||
@onready var load_test_scene_button: Button = $CustomTestScene/LoadTestScene
|
||||
@onready var custom_test_scene_file_dialog: FileDialog = $CustomTestSceneFileDialog
|
||||
@onready var include_all_responses_button: Button = $IncludeAllResponsesButton
|
||||
@onready var states_title: Label = $StatesTitle
|
||||
@onready var globals_list: Tree = $GlobalsList
|
||||
|
||||
var editor_plugin: EditorPlugin
|
||||
var all_globals: Dictionary = {}
|
||||
var enabled_globals: Array = []
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
$NewTemplateButton.text = DialogueConstants.translate("settings.new_template")
|
||||
$MissingTranslationsButton.text = DialogueConstants.translate("settings.missing_keys")
|
||||
$MissingTranslationsHint.text = DialogueConstants.translate("settings.missing_keys_hint")
|
||||
$WrapLinesButton.text = DialogueConstants.translate("settings.wrap_long_lines")
|
||||
$IncludeAllResponsesButton.text = DialogueConstants.translate("settings.include_failed_responses")
|
||||
$CustomTestSceneLabel.text = DialogueConstants.translate("settings.custom_test_scene")
|
||||
$StatesTitle.text = DialogueConstants.translate("settings.states_shortcuts")
|
||||
$StatesMessage.text = DialogueConstants.translate("settings.states_message")
|
||||
$StatesHint.text = DialogueConstants.translate("settings.states_hint")
|
||||
|
||||
|
||||
func prepare() -> void:
|
||||
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 scale: float = editor_plugin.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"))
|
||||
|
||||
missing_translations_button.set_pressed_no_signal(DialogueSettings.get_setting("missing_translations_are_errors", false))
|
||||
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))
|
||||
new_template_button.set_pressed_no_signal(DialogueSettings.get_setting("new_with_template", true))
|
||||
|
||||
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", [])
|
||||
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"))
|
||||
|
||||
|
||||
### Signals
|
||||
|
||||
|
||||
func _on_settings_view_visibility_changed() -> void:
|
||||
prepare()
|
||||
|
||||
|
||||
func _on_missing_translations_button_toggled(button_pressed: bool) -> void:
|
||||
DialogueSettings.set_setting("missing_translations_are_errors", button_pressed)
|
||||
|
||||
|
||||
func _on_wrap_lines_button_toggled(button_pressed: bool) -> void:
|
||||
DialogueSettings.set_setting("wrap_lines", button_pressed)
|
||||
|
||||
|
||||
func _on_include_all_responses_button_toggled(button_pressed: bool) -> void:
|
||||
DialogueSettings.set_setting("include_all_responses", button_pressed)
|
||||
|
||||
|
||||
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(button_pressed):
|
||||
DialogueSettings.set_setting("new_with_template", button_pressed)
|
||||
|
||||
|
||||
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:
|
||||
custom_test_scene_file_dialog.popup_centered()
|
||||
|
||||
|
||||
func _on_custom_test_scene_file_dialog_file_selected(path: String) -> void:
|
||||
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
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue