tool extends Control const DialogueResource = preload("res://addons/dialogue_manager/dialogue_resource.gd") const DialogueConstants = preload("res://addons/dialogue_manager/constants.gd") onready var settings := $Settings onready var parser := $Parser onready var parse_timeout := $ParseTimeout onready var update_checker := $UpdateChecker onready var file_label := $Margin/VBox/Toolbar/FileLabel onready var open_button := $Margin/VBox/Toolbar/OpenButton onready var content := $Margin/VBox/Content onready var title_list := $Margin/VBox/Content/VBox/TitleList onready var error_list := $Margin/VBox/Content/VBox/ErrorList onready var search_toolbar := $Margin/VBox/Content/VBox2/SearchToolbar onready var editor := $Margin/VBox/Content/VBox2/CodeEditor onready var new_dialogue_dialog := $NewDialogueDialog onready var open_dialogue_dialog := $OpenDialogueDialog onready var invalid_dialogue_dialog := $InvalidDialogueDialog onready var settings_dialog := $SettingsDialog onready var insert_menu := $Margin/VBox/Toolbar/InsertMenu onready var translations_menu := $Margin/VBox/Toolbar/TranslationsMenu onready var save_translations_dialog := $SaveTranslationsDialog onready var update_button := $Margin/VBox/Toolbar/UpdateButton onready var error_button := $Margin/VBox/Toolbar/ErrorButton onready var run_node_button := $Margin/VBox/Toolbar/RunButton onready var search_button := $Margin/VBox/Toolbar/SearchButton var plugin var current_resource: DialogueResource var has_changed: bool = false var recent_resources: Array func _ready() -> void: # Hide the editor until we open something set_resource(null) # Check for updates update_checker.check_for_updates() update_button.visible = false update_button.add_color_override("font_color", get_color("success_color", "Editor")) # Set up the button icons $Margin/VBox/Toolbar/NewButton.text = "" $Margin/VBox/Toolbar/NewButton.icon = get_icon("New", "EditorIcons") open_button.text = "" open_button.icon = get_icon("Load", "EditorIcons") $Margin/VBox/Toolbar/SettingsButton.text = "" $Margin/VBox/Toolbar/SettingsButton.icon = get_icon("Tools", "EditorIcons") error_button.text = "" error_button.icon = get_icon("Debug", "EditorIcons") run_node_button.text = "" run_node_button.icon = get_icon("PlayScene", "EditorIcons") search_button.icon = get_icon("Search", "EditorIcons") $Margin/VBox/Toolbar/TranslationsMenu.icon = get_icon("Translation", "EditorIcons") $Margin/VBox/Toolbar/HelpButton.icon = get_icon("Help", "EditorIcons") insert_menu.icon = get_icon("RichTextEffect", "EditorIcons") var popup = insert_menu.get_popup() popup.set_item_icon(0, get_icon("RichTextEffect", "EditorIcons")) popup.set_item_icon(1, get_icon("RichTextEffect", "EditorIcons")) popup.set_item_icon(3, get_icon("Time", "EditorIcons")) popup.set_item_icon(4, get_icon("ViewportSpeed", "EditorIcons")) popup.set_item_icon(5, get_icon("DebugNext", "EditorIcons")) popup = translations_menu.get_popup() popup.set_item_icon(0, get_icon("Translation", "EditorIcons")) popup.set_item_icon(1, get_icon("FileList", "EditorIcons")) search_toolbar.visible = false # Get version number var config = ConfigFile.new() var err = config.load("res://addons/dialogue_manager/plugin.cfg") if err == OK: $Margin/VBox/Toolbar/VersionLabel.text = "v" + config.get_value("plugin", "version") file_label.icon = get_icon("Filesystem", "EditorIcons") insert_menu.get_popup().connect("id_pressed", self, "_on_insert_menu_id_pressed") translations_menu.get_popup().connect("id_pressed", self, "_on_translation_menu_id_pressed") recent_resources = settings.get_editor_value("recent_resources", []) build_open_menu() editor.wrap_enabled = settings.get_editor_value("wrap_lines", false) func apply_changes() -> void: if is_instance_valid(editor) and current_resource != null: current_resource.set("raw_text", editor.text) ResourceSaver.save(current_resource.resource_path, current_resource) parse(true) ### Helpers func build_open_menu() -> void: var menu = open_button.get_popup() menu.clear() menu.add_icon_item(get_icon("Load", "EditorIcons"), "Open...") menu.add_separator() if recent_resources.size() == 0: menu.add_item("No recent files") menu.set_item_disabled(2, true) else: for path in recent_resources: menu.add_icon_item(get_icon("File", "EditorIcons"), path) menu.add_separator() menu.add_item("Clear recent files") if menu.is_connected("index_pressed", self, "_on_open_menu_index_pressed"): menu.disconnect("index_pressed", self, "_on_open_menu_index_pressed") menu.connect("index_pressed", self, "_on_open_menu_index_pressed") func set_resource(value: DialogueResource) -> void: current_resource = value if current_resource: file_label.text = get_nice_file(current_resource.resource_path) file_label.visible = true editor.text = current_resource.raw_text editor.clear_undo_history() content.visible = true error_button.disabled = false run_node_button.disabled = false search_button.disabled = false insert_menu.disabled = false translations_menu.disabled = false _on_CodeEditor_text_changed() has_changed = false else: content.visible = false file_label.visible = false error_button.disabled = true run_node_button.disabled = true search_button.disabled = true insert_menu.disabled = true translations_menu.disabled = true func get_nice_file(file: String) -> String: var bits = file.replace("res://", "").split("/") if bits.size() == 1: return bits[0] else: return "%s/%s" % [bits[bits.size() - 2], bits[bits.size() - 1]] func open_resource(resource: DialogueResource) -> void: apply_upgrades(resource) set_resource(resource) # Add this to our list of recent resources if resource.resource_path in recent_resources: recent_resources.erase(resource.resource_path) recent_resources.insert(0, resource.resource_path) settings.set_editor_value("recent_resources", recent_resources) build_open_menu() parse(true) func open_resource_from_path(path: String) -> void: var resource = load(path) if resource is DialogueResource: open_resource(resource) else: invalid_dialogue_dialog.popup_centered() func apply_upgrades(resource: DialogueResource) -> void: if resource == null: return if not resource is DialogueResource: return var lines = resource.raw_text.split("\n") for i in range(0, lines.size()): var line: String = lines[i] if resource.syntax_version == 0: if line.begins_with("# "): line = "~ " + line.substr(2).replace(" ", "_") line = line.replace("// ", "# ") if "goto #" in line: var index = line.find("goto # ") line = line.substr(0, index) + "=> " + line.substr(index + 7).replace(" ", "_") lines[i] = line resource.set("syntax_version", DialogueConstants.SYNTAX_VERSION) resource.set("raw_text", lines.join("\n")) func parse(force_show_errors: bool = false) -> void: if current_resource == null: return if not has_changed and not force_show_errors: return var result = parser.parse(editor.text) if settings.get_editor_value("store_compiler_results", true): current_resource.set("titles", result.titles) current_resource.set("lines", result.lines) current_resource.set("errors", result.errors) else: current_resource.set("titles", {}) current_resource.set("lines", {}) current_resource.set("errors", []) ResourceSaver.save(current_resource.resource_path, current_resource) has_changed = false if force_show_errors or settings.get_editor_value("check_for_errors") or error_list.errors.size() > 0: error_list.errors = result.errors for line_number in range(0, editor.get_line_count() - 1): editor.set_line_as_bookmark(line_number, false) for error in result.errors: if error.get("line") == line_number: editor.set_line_as_bookmark(line_number, true) func generate_translations_keys() -> void: randomize() seed(OS.get_unix_time()) var lines: PoolStringArray = editor.text.split("\n") var key_regex = RegEx.new() key_regex.compile("\\[TR:(?.*?)\\]") # 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(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 l == "" or l.begins_with("#"): continue if l.begins_with("if ") or l.begins_with("elif ") or l.begins_with("else") or l.begins_with("endif"): continue if l.begins_with("~ "): continue if l.begins_with("do ") or l.begins_with("set "): continue if l.begins_with("=>"): continue if "[TR:" 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) # See if identical text already has a key var text = "" if l.begins_with("- "): text = parser.extract_response(l) else: text = l.substr(l.find(":") + 1) var index = known_keys.values().find(text) if index > -1: key = known_keys.keys()[index] lines[i] = line.replace(text, text + " [TR:%s]" % key) known_keys[key] = text editor.text = lines.join("\n") _on_CodeEditor_text_changed() func save_translations(path: String) -> void: var file = File.new() # If the file exists, open it first and work out which keys are already in it var existing_csv = {} var commas = [] if file.file_exists(path): file.open(path, File.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("") existing_csv[line[0]] = line file.close() # Start a new file file.open(path, File.WRITE) if not file.file_exists(path): file.store_csv_line(["keys", "en"]) # Write our translations to file var known_keys: PoolStringArray = [] var dialogue = parser.parse(editor.text).get("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.get("type") in [DialogueConstants.TYPE_DIALOGUE, DialogueConstants.TYPE_RESPONSE]: continue if line.get("text") in known_keys: continue known_keys.append(line.get("text")) if existing_csv.has(line.get("translation_key")): var existing_line = existing_csv.get(line.get("translation_key")) existing_line[1] = line.get("text") lines_to_save.append(existing_line) existing_csv.erase(line.get("translation_key")) else: known_keys.append(line.get("text")) lines_to_save.append(PoolStringArray([line.get("translation_key"), line.get("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() plugin.get_editor_interface().get_resource_filesystem().scan() plugin.get_editor_interface().get_file_system_dock().navigate_to_path(path) ### Signals func _on_open_menu_index_pressed(index): var item = open_button.get_popup().get_item_text(index) match item: "Open...": open_dialogue_dialog.popup_centered() "Clear recent files": recent_resources.clear() settings.set_editor_value("recent_resources", recent_resources) build_open_menu() _: open_resource_from_path(item) func _on_insert_menu_id_pressed(id): match id: 0: editor.insert_bbcode("[wave amp=25 freq=5]", "[/wave]") 1: editor.insert_bbcode("[shake rate=20 level=10]", "[/shake]") 3: editor.insert_bbcode("[wait=1]") 4: editor.insert_bbcode("[speed=0.2]") 5: editor.insert_bbcode("[next=auto]") func _on_translation_menu_id_pressed(id): match id: 0: generate_translations_keys() 1: var filename = current_resource.resource_path.get_file().replace(".tres", ".csv") var path = settings.get_editor_value("last_csv_path", current_resource.resource_path.get_base_dir()) + "/" + filename save_translations_dialog.current_path = path save_translations_dialog.popup_centered() func _on_CodeEditor_text_changed(): has_changed = true title_list.titles = editor.get_titles() parse_timeout.start(1) func _on_NewButton_pressed(): new_dialogue_dialog.popup_centered() func _on_NewDialogueDialog_file_selected(path): var resource = DialogueResource.new() resource.take_over_path(path) ResourceSaver.save(path, resource) open_resource(resource) func _on_FileLabel_pressed(): var file_system = plugin.get_editor_interface().get_file_system_dock() file_system.navigate_to_path(current_resource.resource_path) func _on_SettingsButton_pressed(): settings_dialog.popup_centered() func _on_CodeEditor_active_title_changed(title): title_list.select_title(title) settings.set_editor_value("run_title", title) run_node_button.hint_tooltip = "Play the test scene using \"%s\"" % title func _on_ParseTimeout_timeout(): parse_timeout.stop() parse() func _on_TitleList_title_clicked(title): editor.go_to_title(title) func _on_OpenDialogueDialog_file_selected(path): open_resource_from_path(path) func _on_OpenDialogueDialog_confirmed(): open_resource_from_path(open_dialogue_dialog.current_path) func _on_SettingsDialog_popup_hide(): parse(true) editor.wrap_enabled = settings.get_editor_value("wrap_lines", false) editor.grab_focus() func _on_ErrorList_error_pressed(error): editor.cursor_set_line(error.get("line")) func _on_HelpButton_pressed(): OS.shell_open("https://github.com/nathanhoad/godot_dialogue_manager") func _on_SaveTranslationsDialog_file_selected(path): settings.set_editor_value("last_csv_path", path.get_base_dir()) save_translations(path) func _on_UpdateChecker_has_update(version, url): update_button.visible = true update_button.text = "v" + version + " available!" func _on_UpdateButton_pressed(): OS.shell_open(update_checker.plugin_url) func _on_ErrorButton_pressed(): parse(true) func _on_SettingsDialog_script_button_pressed(path): plugin.get_editor_interface().edit_resource(load(path)) func _on_RunButton_pressed(): if settings.has_editor_value("run_title"): settings.set_editor_value("run_resource", current_resource.resource_path) plugin.get_editor_interface().play_custom_scene("res://addons/dialogue_manager/views/test_scene.tscn") func _on_SearchButton_toggled(button_pressed): if editor.last_selection_text: search_toolbar.input.text = editor.last_selection_text search_toolbar.visible = button_pressed func _on_SearchToolbar_close_requested(): search_button.pressed = false search_toolbar.visible = false editor.grab_focus() func _on_SearchToolbar_open_requested(): search_button.pressed = true search_toolbar.visible = true