From a0792afb81369ee1b3a9af7678739ee147fa2038 Mon Sep 17 00:00:00 2001 From: cakipaul Date: Mon, 10 Mar 2025 20:58:01 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20dialogue=20manager=20v3.4.?= =?UTF-8?q?0=20for=20Godot=204.4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/debug_menu/debug_menu.gd.uid | 1 + addons/debug_menu/debug_menu.tscn | 2 +- addons/debug_menu/plugin.gd.uid | 1 + addons/dialogue_manager/DialogueManager.cs | 58 +- .../dialogue_manager/DialogueManager.cs.uid | 1 + .../dialogue_manager/compiler/compilation.gd | 1078 ++++++++++ .../compiler/compilation.gd.uid | 1 + .../compiler/compiled_line.gd | 157 ++ .../compiler/compiled_line.gd.uid | 1 + addons/dialogue_manager/compiler/compiler.gd | 51 + .../dialogue_manager/compiler/compiler.gd.uid | 1 + .../compiler/compiler_regex.gd | 49 + .../compiler/compiler_regex.gd.uid | 1 + .../compiler/compiler_result.gd | 27 + .../compiler/compiler_result.gd.uid | 1 + .../compiler/expression_parser.gd | 497 +++++ .../compiler/expression_parser.gd.uid | 1 + .../compiler/resolved_goto_data.gd | 68 + .../compiler/resolved_goto_data.gd.uid | 1 + .../compiler/resolved_line_data.gd | 167 ++ .../compiler/resolved_line_data.gd.uid | 1 + .../compiler/resolved_tag_data.gd | 26 + .../compiler/resolved_tag_data.gd.uid | 1 + addons/dialogue_manager/compiler/tree_line.gd | 44 + .../compiler/tree_line.gd.uid | 1 + .../dialogue_manager/components/code_edit.gd | 27 +- .../components/code_edit.gd.uid | 1 + .../components/code_edit.tscn | 4 +- .../code_edit_syntax_highlighter.gd | 499 ++--- .../code_edit_syntax_highlighter.gd.uid | 1 + .../components/download_update_panel.gd | 2 +- .../components/download_update_panel.gd.uid | 1 + .../components/download_update_panel.tscn | 2 +- .../editor_property/editor_property.gd.uid | 1 + .../editor_property_control.gd | 10 +- .../editor_property_control.gd.uid | 1 + .../editor_property_control.tscn | 2 +- .../editor_property/resource_button.gd.uid | 1 + .../editor_property/resource_button.tscn | 2 +- .../components/errors_panel.gd | 4 +- .../components/errors_panel.gd.uid | 1 + .../components/errors_panel.tscn | 6 +- .../components/files_list.gd.uid | 1 + .../components/files_list.tscn | 2 +- .../components/find_in_files.gd | 2 +- .../components/find_in_files.gd.uid | 1 + .../components/find_in_files.tscn | 2 +- .../components/parse_result.gd | 10 - addons/dialogue_manager/components/parser.gd | 1798 ----------------- .../components/resolved_line_data.gd | 15 - .../components/resolved_tag_data.gd | 10 - .../components/search_and_replace.gd.uid | 1 + .../components/search_and_replace.tscn | 2 +- .../components/title_list.gd.uid | 1 + .../components/title_list.tscn | 2 +- .../components/update_button.gd | 6 +- .../components/update_button.gd.uid | 1 + .../components/update_button.tscn | 2 +- addons/dialogue_manager/constants.gd | 46 +- addons/dialogue_manager/constants.gd.uid | 1 + addons/dialogue_manager/dialogue_label.gd | 5 +- addons/dialogue_manager/dialogue_label.gd.uid | 1 + addons/dialogue_manager/dialogue_label.tscn | 2 +- addons/dialogue_manager/dialogue_line.gd | 17 +- addons/dialogue_manager/dialogue_line.gd.uid | 1 + addons/dialogue_manager/dialogue_manager.gd | 1069 +++++----- .../dialogue_manager/dialogue_manager.gd.uid | 1 + addons/dialogue_manager/dialogue_resource.gd | 21 +- .../dialogue_manager/dialogue_resource.gd.uid | 1 + addons/dialogue_manager/dialogue_response.gd | 5 +- .../dialogue_manager/dialogue_response.gd.uid | 1 + ...ses_menu.gd => dialogue_responses_menu.gd} | 16 +- .../dialogue_responses_menu.gd.uid | 1 + .../editor_translation_parser_plugin.gd | 45 +- .../editor_translation_parser_plugin.gd.uid | 1 + .../example_balloon/ExampleBalloon.cs | 47 +- .../example_balloon/ExampleBalloon.cs.uid | 1 + .../example_balloon/example_balloon.gd | 113 +- .../example_balloon/example_balloon.gd.uid | 1 + .../example_balloon/example_balloon.tscn | 4 +- .../small_example_balloon.tscn | 7 +- addons/dialogue_manager/import_plugin.gd | 45 +- addons/dialogue_manager/import_plugin.gd.uid | 1 + addons/dialogue_manager/inspector_plugin.gd | 2 +- .../dialogue_manager/inspector_plugin.gd.uid | 1 + addons/dialogue_manager/l10n/en.po | 109 +- addons/dialogue_manager/l10n/es.po | 79 - addons/dialogue_manager/l10n/translations.pot | 105 +- addons/dialogue_manager/l10n/uk.po | 160 +- addons/dialogue_manager/l10n/zh.po | 66 - addons/dialogue_manager/l10n/zh_TW.po | 66 - addons/dialogue_manager/plugin.cfg | 4 +- addons/dialogue_manager/plugin.cfg.uid | 1 + addons/dialogue_manager/plugin.gd | 155 +- addons/dialogue_manager/plugin.gd.uid | 1 + addons/dialogue_manager/settings.gd | 237 ++- addons/dialogue_manager/settings.gd.uid | 1 + addons/dialogue_manager/test_scene.gd | 21 +- addons/dialogue_manager/test_scene.gd.uid | 1 + addons/dialogue_manager/test_scene.tscn | 5 +- addons/dialogue_manager/utilities/builtins.gd | 37 +- .../utilities/builtins.gd.uid | 1 + .../dialogue_cache.gd | 30 +- .../utilities/dialogue_cache.gd.uid | 1 + addons/dialogue_manager/views/main_view.gd | 376 ++-- .../dialogue_manager/views/main_view.gd.uid | 1 + addons/dialogue_manager/views/main_view.tscn | 49 +- .../dialogue_manager/views/settings_view.gd | 288 --- .../dialogue_manager/views/settings_view.tscn | 233 --- addons/gif-importer/importer_plugin.gd.uid | 1 + addons/gif-importer/plugin.gd.uid | 1 + addons/godotgif/godotgif.gdextension.uid | 1 + .../loaders/FileStatistics.gd.uid | 1 + .../loaders/ProjectStatistics.gd.uid | 1 + .../extensions/CSharpStatistics.gd.uid | 1 + .../extensions/ConfigFileStatistics.gd.uid | 1 + .../extensions/GDScriptStatistics.gd.uid | 1 + .../loaders/extensions/JSONStatistics.gd.uid | 1 + .../extensions/MarkdownStatistics.gd.uid | 1 + .../extensions/ResourceStatistics.gd.uid | 1 + .../loaders/extensions/YAMLStatistics.gd.uid | 1 + .../project-statistics/nodes/MiscView.gd.uid | 1 + addons/project-statistics/nodes/MiscView.tscn | 2 +- .../project-statistics/nodes/Overview.gd.uid | 1 + addons/project-statistics/nodes/Overview.tscn | 2 +- .../nodes/PanelContainer.gd.uid | 1 + .../nodes/ResourcesView.gd.uid | 1 + .../nodes/ResourcesView.tscn | 2 +- .../nodes/ScenesView.gd.uid | 1 + .../project-statistics/nodes/ScenesView.tscn | 2 +- .../nodes/ScriptsView.gd.uid | 1 + .../project-statistics/nodes/ScriptsView.tscn | 2 +- .../nodes/StatisticsPreview.gd.uid | 1 + .../nodes/StatisticsPreview.tscn | 2 +- .../nodes/StatisticsView.gd.uid | 1 + .../project-statistics/nodes/TreeView.gd.uid | 1 + .../nodes/charts/ChartData.gd.uid | 1 + .../nodes/charts/PieChart.gd.uid | 1 + .../nodes/charts/PieGraph.gd.uid | 1 + .../nodes/charts/PieGraph.tscn | 4 +- addons/project-statistics/plugin.gd.uid | 1 + addons/property-inspector/inspector.gd.uid | 1 + addons/property-inspector/plugin.gd.uid | 1 + .../pro_animated_sprite.gd.uid | 1 + .../pro_animated_sprite.tscn | 2 +- .../pro_animation_action_editor.gd.uid | 1 + .../pro_animation_move_editor.gd.uid | 1 + .../state_action_res.gd.uid | 1 + .../state_move_res.gd.uid | 1 + .../editor_color_setter.gd.uid | 1 + .../editor_icon_button.gd.uid | 1 + .../editor_view.gd.uid | 1 + .../editor_view.tscn | 34 +- .../formats_edit/edit_base.gd.uid | 1 + .../formats_edit/edit_csv.gd.uid | 1 + .../formats_edit/edit_tres.gd.uid | 1 + .../formats_export/export_csv.gd.uid | 1 + .../formats_import/import_csv.gd.uid | 1 + .../import_export/import_export_dialog.gd.uid | 1 + .../import_export/import_export_dialog.tscn | 6 +- .../import_export_enum_format.gd.uid | 1 + .../import_export_enum_format.tscn | 2 +- .../import_export/property_list_item.gd.uid | 1 + .../import_export/property_list_item.tscn | 2 +- .../import_export/spreadsheet_import.gd.uid | 1 + .../main_screen/column_header_manager.gd.uid | 1 + .../main_screen/expression_textfield.gd.uid | 1 + .../main_screen/input_handler.gd.uid | 1 + .../main_screen/recent_paths.gd.uid | 1 + .../main_screen/selection_actions.gd.uid | 1 + .../main_screen/selection_actions.tscn | 10 +- .../main_screen/selection_manager.gd.uid | 1 + .../main_screen/table_header.gd.uid | 1 + .../main_screen/table_header.tscn | 4 +- .../main_screen/table_pages.gd.uid | 1 + .../resources_spreadsheet_view/plugin.gd.uid | 1 + .../settings_grid.gd.uid | 1 + .../settings_grid.tscn | 2 +- .../text_editing_utils.gd.uid | 1 + .../typed_cells/cell_editor.gd.uid | 1 + .../typed_cells/cell_editor_array.gd.uid | 1 + .../typed_cells/cell_editor_bool.gd.uid | 1 + .../typed_cells/cell_editor_color.gd.uid | 1 + .../typed_cells/cell_editor_dict.gd.uid | 1 + .../typed_cells/cell_editor_enum.gd.uid | 1 + .../typed_cells/cell_editor_enum_array.gd.uid | 1 + .../typed_cells/cell_editor_resource.gd.uid | 1 + .../typed_cells/cell_editor_string.gd.uid | 1 + .../typed_editors/dock_array.gd.uid | 1 + .../typed_editors/dock_array.tscn | 8 +- .../typed_editors/dock_base.gd.uid | 1 + .../typed_editors/dock_color.gd.uid | 1 + .../typed_editors/dock_color.tscn | 2 +- .../typed_editors/dock_dict.gd.uid | 1 + .../typed_editors/dock_dict.tscn | 8 +- .../typed_editors/dock_enum_array.gd.uid | 1 + .../typed_editors/dock_enum_array.tscn | 8 +- .../typed_editors/dock_number.gd.uid | 1 + .../typed_editors/dock_number.tscn | 2 +- .../typed_editors/dock_texture.gd.uid | 1 + .../typed_editors/dock_texture.tscn | 2 +- 201 files changed, 3985 insertions(+), 4335 deletions(-) create mode 100644 addons/debug_menu/debug_menu.gd.uid create mode 100644 addons/debug_menu/plugin.gd.uid create mode 100644 addons/dialogue_manager/DialogueManager.cs.uid create mode 100644 addons/dialogue_manager/compiler/compilation.gd create mode 100644 addons/dialogue_manager/compiler/compilation.gd.uid create mode 100644 addons/dialogue_manager/compiler/compiled_line.gd create mode 100644 addons/dialogue_manager/compiler/compiled_line.gd.uid create mode 100644 addons/dialogue_manager/compiler/compiler.gd create mode 100644 addons/dialogue_manager/compiler/compiler.gd.uid create mode 100644 addons/dialogue_manager/compiler/compiler_regex.gd create mode 100644 addons/dialogue_manager/compiler/compiler_regex.gd.uid create mode 100644 addons/dialogue_manager/compiler/compiler_result.gd create mode 100644 addons/dialogue_manager/compiler/compiler_result.gd.uid create mode 100644 addons/dialogue_manager/compiler/expression_parser.gd create mode 100644 addons/dialogue_manager/compiler/expression_parser.gd.uid create mode 100644 addons/dialogue_manager/compiler/resolved_goto_data.gd create mode 100644 addons/dialogue_manager/compiler/resolved_goto_data.gd.uid create mode 100644 addons/dialogue_manager/compiler/resolved_line_data.gd create mode 100644 addons/dialogue_manager/compiler/resolved_line_data.gd.uid create mode 100644 addons/dialogue_manager/compiler/resolved_tag_data.gd create mode 100644 addons/dialogue_manager/compiler/resolved_tag_data.gd.uid create mode 100644 addons/dialogue_manager/compiler/tree_line.gd create mode 100644 addons/dialogue_manager/compiler/tree_line.gd.uid create mode 100644 addons/dialogue_manager/components/code_edit.gd.uid create mode 100644 addons/dialogue_manager/components/code_edit_syntax_highlighter.gd.uid create mode 100644 addons/dialogue_manager/components/download_update_panel.gd.uid create mode 100644 addons/dialogue_manager/components/editor_property/editor_property.gd.uid create mode 100644 addons/dialogue_manager/components/editor_property/editor_property_control.gd.uid create mode 100644 addons/dialogue_manager/components/editor_property/resource_button.gd.uid create mode 100644 addons/dialogue_manager/components/errors_panel.gd.uid create mode 100644 addons/dialogue_manager/components/files_list.gd.uid create mode 100644 addons/dialogue_manager/components/find_in_files.gd.uid delete mode 100644 addons/dialogue_manager/components/parse_result.gd delete mode 100644 addons/dialogue_manager/components/parser.gd delete mode 100644 addons/dialogue_manager/components/resolved_line_data.gd delete mode 100644 addons/dialogue_manager/components/resolved_tag_data.gd create mode 100644 addons/dialogue_manager/components/search_and_replace.gd.uid create mode 100644 addons/dialogue_manager/components/title_list.gd.uid create mode 100644 addons/dialogue_manager/components/update_button.gd.uid create mode 100644 addons/dialogue_manager/constants.gd.uid create mode 100644 addons/dialogue_manager/dialogue_label.gd.uid mode change 100644 => 100755 addons/dialogue_manager/dialogue_line.gd create mode 100644 addons/dialogue_manager/dialogue_line.gd.uid create mode 100644 addons/dialogue_manager/dialogue_manager.gd.uid create mode 100644 addons/dialogue_manager/dialogue_resource.gd.uid create mode 100644 addons/dialogue_manager/dialogue_response.gd.uid rename addons/dialogue_manager/{dialogue_reponses_menu.gd => dialogue_responses_menu.gd} (91%) create mode 100644 addons/dialogue_manager/dialogue_responses_menu.gd.uid create mode 100644 addons/dialogue_manager/editor_translation_parser_plugin.gd.uid create mode 100644 addons/dialogue_manager/example_balloon/ExampleBalloon.cs.uid create mode 100644 addons/dialogue_manager/example_balloon/example_balloon.gd.uid create mode 100644 addons/dialogue_manager/import_plugin.gd.uid create mode 100644 addons/dialogue_manager/inspector_plugin.gd.uid create mode 100644 addons/dialogue_manager/plugin.cfg.uid create mode 100644 addons/dialogue_manager/plugin.gd.uid create mode 100644 addons/dialogue_manager/settings.gd.uid create mode 100644 addons/dialogue_manager/test_scene.gd.uid create mode 100644 addons/dialogue_manager/utilities/builtins.gd.uid rename addons/dialogue_manager/{components => utilities}/dialogue_cache.gd (84%) create mode 100644 addons/dialogue_manager/utilities/dialogue_cache.gd.uid create mode 100644 addons/dialogue_manager/views/main_view.gd.uid delete mode 100644 addons/dialogue_manager/views/settings_view.gd delete mode 100644 addons/dialogue_manager/views/settings_view.tscn create mode 100644 addons/gif-importer/importer_plugin.gd.uid create mode 100644 addons/gif-importer/plugin.gd.uid create mode 100644 addons/godotgif/godotgif.gdextension.uid create mode 100644 addons/project-statistics/loaders/FileStatistics.gd.uid create mode 100644 addons/project-statistics/loaders/ProjectStatistics.gd.uid create mode 100644 addons/project-statistics/loaders/extensions/CSharpStatistics.gd.uid create mode 100644 addons/project-statistics/loaders/extensions/ConfigFileStatistics.gd.uid create mode 100644 addons/project-statistics/loaders/extensions/GDScriptStatistics.gd.uid create mode 100644 addons/project-statistics/loaders/extensions/JSONStatistics.gd.uid create mode 100644 addons/project-statistics/loaders/extensions/MarkdownStatistics.gd.uid create mode 100644 addons/project-statistics/loaders/extensions/ResourceStatistics.gd.uid create mode 100644 addons/project-statistics/loaders/extensions/YAMLStatistics.gd.uid create mode 100644 addons/project-statistics/nodes/MiscView.gd.uid create mode 100644 addons/project-statistics/nodes/Overview.gd.uid create mode 100644 addons/project-statistics/nodes/PanelContainer.gd.uid create mode 100644 addons/project-statistics/nodes/ResourcesView.gd.uid create mode 100644 addons/project-statistics/nodes/ScenesView.gd.uid create mode 100644 addons/project-statistics/nodes/ScriptsView.gd.uid create mode 100644 addons/project-statistics/nodes/StatisticsPreview.gd.uid create mode 100644 addons/project-statistics/nodes/StatisticsView.gd.uid create mode 100644 addons/project-statistics/nodes/TreeView.gd.uid create mode 100644 addons/project-statistics/nodes/charts/ChartData.gd.uid create mode 100644 addons/project-statistics/nodes/charts/PieChart.gd.uid create mode 100644 addons/project-statistics/nodes/charts/PieGraph.gd.uid create mode 100644 addons/project-statistics/plugin.gd.uid create mode 100644 addons/property-inspector/inspector.gd.uid create mode 100644 addons/property-inspector/plugin.gd.uid create mode 100644 addons/property-inspector/pro_animation_sprite2d/pro_animated_sprite.gd.uid create mode 100644 addons/property-inspector/pro_animation_sprite2d/pro_animation_action_editor.gd.uid create mode 100644 addons/property-inspector/pro_animation_sprite2d/pro_animation_move_editor.gd.uid create mode 100644 addons/property-inspector/pro_animation_sprite2d/state_action_res.gd.uid create mode 100644 addons/property-inspector/pro_animation_sprite2d/state_move_res.gd.uid create mode 100644 addons/resources_spreadsheet_view/editor_color_setter.gd.uid create mode 100644 addons/resources_spreadsheet_view/editor_icon_button.gd.uid create mode 100644 addons/resources_spreadsheet_view/editor_view.gd.uid create mode 100644 addons/resources_spreadsheet_view/import_export/formats_edit/edit_base.gd.uid create mode 100644 addons/resources_spreadsheet_view/import_export/formats_edit/edit_csv.gd.uid create mode 100644 addons/resources_spreadsheet_view/import_export/formats_edit/edit_tres.gd.uid create mode 100644 addons/resources_spreadsheet_view/import_export/formats_export/export_csv.gd.uid create mode 100644 addons/resources_spreadsheet_view/import_export/formats_import/import_csv.gd.uid create mode 100644 addons/resources_spreadsheet_view/import_export/import_export_dialog.gd.uid create mode 100644 addons/resources_spreadsheet_view/import_export/import_export_enum_format.gd.uid create mode 100644 addons/resources_spreadsheet_view/import_export/property_list_item.gd.uid create mode 100644 addons/resources_spreadsheet_view/import_export/spreadsheet_import.gd.uid create mode 100644 addons/resources_spreadsheet_view/main_screen/column_header_manager.gd.uid create mode 100644 addons/resources_spreadsheet_view/main_screen/expression_textfield.gd.uid create mode 100644 addons/resources_spreadsheet_view/main_screen/input_handler.gd.uid create mode 100644 addons/resources_spreadsheet_view/main_screen/recent_paths.gd.uid create mode 100644 addons/resources_spreadsheet_view/main_screen/selection_actions.gd.uid create mode 100644 addons/resources_spreadsheet_view/main_screen/selection_manager.gd.uid create mode 100644 addons/resources_spreadsheet_view/main_screen/table_header.gd.uid create mode 100644 addons/resources_spreadsheet_view/main_screen/table_pages.gd.uid create mode 100644 addons/resources_spreadsheet_view/plugin.gd.uid create mode 100644 addons/resources_spreadsheet_view/settings_grid.gd.uid create mode 100644 addons/resources_spreadsheet_view/text_editing_utils.gd.uid create mode 100644 addons/resources_spreadsheet_view/typed_cells/cell_editor.gd.uid create mode 100644 addons/resources_spreadsheet_view/typed_cells/cell_editor_array.gd.uid create mode 100644 addons/resources_spreadsheet_view/typed_cells/cell_editor_bool.gd.uid create mode 100644 addons/resources_spreadsheet_view/typed_cells/cell_editor_color.gd.uid create mode 100644 addons/resources_spreadsheet_view/typed_cells/cell_editor_dict.gd.uid create mode 100644 addons/resources_spreadsheet_view/typed_cells/cell_editor_enum.gd.uid create mode 100644 addons/resources_spreadsheet_view/typed_cells/cell_editor_enum_array.gd.uid create mode 100644 addons/resources_spreadsheet_view/typed_cells/cell_editor_resource.gd.uid create mode 100644 addons/resources_spreadsheet_view/typed_cells/cell_editor_string.gd.uid create mode 100644 addons/resources_spreadsheet_view/typed_editors/dock_array.gd.uid create mode 100644 addons/resources_spreadsheet_view/typed_editors/dock_base.gd.uid create mode 100644 addons/resources_spreadsheet_view/typed_editors/dock_color.gd.uid create mode 100644 addons/resources_spreadsheet_view/typed_editors/dock_dict.gd.uid create mode 100644 addons/resources_spreadsheet_view/typed_editors/dock_enum_array.gd.uid create mode 100644 addons/resources_spreadsheet_view/typed_editors/dock_number.gd.uid create mode 100644 addons/resources_spreadsheet_view/typed_editors/dock_texture.gd.uid diff --git a/addons/debug_menu/debug_menu.gd.uid b/addons/debug_menu/debug_menu.gd.uid new file mode 100644 index 00000000..265ae86a --- /dev/null +++ b/addons/debug_menu/debug_menu.gd.uid @@ -0,0 +1 @@ +uid://pjsl6kq3jfwh diff --git a/addons/debug_menu/debug_menu.tscn b/addons/debug_menu/debug_menu.tscn index b30d5741..ef5c73ec 100644 --- a/addons/debug_menu/debug_menu.tscn +++ b/addons/debug_menu/debug_menu.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=3 format=3 uid="uid://cggqb75a8w8r"] -[ext_resource type="Script" path="res://addons/debug_menu/debug_menu.gd" id="1_p440y"] +[ext_resource type="Script" uid="uid://pjsl6kq3jfwh" path="res://addons/debug_menu/debug_menu.gd" id="1_p440y"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ki0n8"] bg_color = Color(0, 0, 0, 0.25098) diff --git a/addons/debug_menu/plugin.gd.uid b/addons/debug_menu/plugin.gd.uid new file mode 100644 index 00000000..96187f6b --- /dev/null +++ b/addons/debug_menu/plugin.gd.uid @@ -0,0 +1 @@ +uid://7hcv3wxdt3h0 diff --git a/addons/dialogue_manager/DialogueManager.cs b/addons/dialogue_manager/DialogueManager.cs index c7b7a5c2..20351c08 100644 --- a/addons/dialogue_manager/DialogueManager.cs +++ b/addons/dialogue_manager/DialogueManager.cs @@ -16,13 +16,15 @@ namespace DialogueManagerRuntime PO } - public partial class DialogueManager : Node + public partial class DialogueManager : RefCounted { + public delegate void DialogueStartedEventHandler(Resource dialogueResource); public delegate void PassedTitleEventHandler(string title); public delegate void GotDialogueEventHandler(DialogueLine dialogueLine); public delegate void MutatedEventHandler(Dictionary mutation); public delegate void DialogueEndedEventHandler(Resource dialogueResource); + public static DialogueStartedEventHandler? DialogueStarted; public static PassedTitleEventHandler? PassedTitle; public static GotDialogueEventHandler? GotDialogue; public static MutatedEventHandler? Mutated; @@ -38,6 +40,7 @@ namespace DialogueManagerRuntime if (instance == null) { instance = Engine.GetSingleton("DialogueManager"); + instance.Connect("bridge_dialogue_started", Callable.From((Resource dialogueResource) => DialogueStarted?.Invoke(dialogueResource))); } return instance; } @@ -86,11 +89,6 @@ namespace DialogueManagerRuntime instance.Connect("dialogue_ended", Callable.From((Resource dialogueResource) => DialogueEnded?.Invoke(dialogueResource))); } - public void Prepare() - { - Prepare(Instance); - } - public static async Task GetSingleton() { @@ -170,16 +168,32 @@ namespace DialogueManagerRuntime } - public bool ThingHasMethod(GodotObject thing, string method) + public bool ThingHasMethod(GodotObject thing, string method, Array args) { - MethodInfo? info = thing.GetType().GetMethod(method, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public); - return info != null; + var methodInfos = thing.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly); + foreach (var methodInfo in methodInfos) + { + if (methodInfo.Name == method && args.Count == methodInfo.GetParameters().Length) + { + return true; + } + } + + return false; } public async void ResolveThingMethod(GodotObject thing, string method, Array args) { - MethodInfo? info = thing.GetType().GetMethod(method, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public); + MethodInfo? info = null; + var methodInfos = thing.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly); + foreach (var methodInfo in methodInfos) + { + if (methodInfo.Name == method && args.Count == methodInfo.GetParameters().Length) + { + info = methodInfo; + } + } if (info == null) return; @@ -216,18 +230,15 @@ namespace DialogueManagerRuntime if (result is Task taskResult) { - // await Tasks and handle result if it is a Task await taskResult; - var taskType = taskResult.GetType(); - if (taskType.IsGenericType && taskType.GetGenericTypeDefinition() == typeof(Task<>)) + try { - var resultProperty = taskType.GetProperty("Result"); - var taskResultValue = resultProperty.GetValue(taskResult); - EmitSignal(SignalName.Resolved, (Variant)taskResultValue); + Variant value = (Variant)taskResult.GetType().GetProperty("Result").GetValue(taskResult); + EmitSignal(SignalName.Resolved, value); } - else + catch (Exception err) { - EmitSignal(SignalName.Resolved, null); + EmitSignal(SignalName.Resolved); } } else @@ -313,6 +324,12 @@ namespace DialogueManagerRuntime get => inline_mutations; } + private Array concurrent_lines = new Array(); + public Array ConcurrentLines + { + get => concurrent_lines; + } + private Array extra_game_states = new Array(); public Array ExtraGameStates { @@ -338,6 +355,11 @@ namespace DialogueManagerRuntime time = (string)data.Get("time"); tags = (Array)data.Get("tags"); + foreach (var concurrent_line_data in (Array)data.Get("concurrent_lines")) + { + concurrent_lines.Add(new DialogueLine(concurrent_line_data)); + } + foreach (var response in (Array)data.Get("responses")) { responses.Add(new DialogueResponse(response)); diff --git a/addons/dialogue_manager/DialogueManager.cs.uid b/addons/dialogue_manager/DialogueManager.cs.uid new file mode 100644 index 00000000..8d5ba927 --- /dev/null +++ b/addons/dialogue_manager/DialogueManager.cs.uid @@ -0,0 +1 @@ +uid://c4c5lsrwy3opj diff --git a/addons/dialogue_manager/compiler/compilation.gd b/addons/dialogue_manager/compiler/compilation.gd new file mode 100644 index 00000000..32b4ffb5 --- /dev/null +++ b/addons/dialogue_manager/compiler/compilation.gd @@ -0,0 +1,1078 @@ +## A single compilation instance of some dialogue. +class_name DMCompilation extends RefCounted + + +#region Compilation locals + + +## A list of file paths that were imported by this file. +var imported_paths: PackedStringArray = [] +## A list of state names from "using" clauses. +var using_states: PackedStringArray = [] +## A map of titles in this file. +var titles: Dictionary = {} +## The first encountered title in this file. +var first_title: String = "" +## A list of character names in this file. +var character_names: PackedStringArray = [] +## A list of any compilation errors. +var errors: Array[Dictionary] = [] +## A map of all compiled lines. +var lines: Dictionary = {} +## A flattened and simplified map of compiled lines for storage in a resource. +var data: Dictionary = {} + + +#endregion + +#region Internal variables + + +# A list of all [RegEx] references +var regex: DMCompilerRegEx = DMCompilerRegEx.new() +# For parsing condition/mutation expressions +var expression_parser: DMExpressionParser = DMExpressionParser.new() + +# A map of titles that came from imported files. +var _imported_titles: Dictionary = {} +# Used to keep track of circular imports. +var _imported_line_map: Dictionary = {} +# The number of imported lines. +var _imported_line_count: int = 0 +# A list of already encountered static line IDs. +var _known_translation_keys: Dictionary = {} +# A noop for retrieving the next line without conditions. +var _first: Callable = func(_s): return true + +# Title jumps are adjusted as they are parsed so any goto lines might need to be adjusted after they are first seen. +var _goto_lines: Dictionary = {} + + +#endregion + +#region Main + + +## Compile some text. +func compile(text: String, path: String = ".") -> Error: + titles = {} + character_names = [] + + parse_line_tree(build_line_tree(inject_imported_files(text + "\n=> END", path))) + + # Convert the compiles lines to a Dictionary so they can be stored. + for id in lines: + var line: DMCompiledLine = lines[id] + data[id] = line.to_data() + + if errors.size() > 0: + return ERR_PARSE_ERROR + + return OK + + +## Inject any imported files +func inject_imported_files(text: String, path: String) -> PackedStringArray: + # Work out imports + var known_imports: Dictionary = {} + + # Include the base file path so that we can get around circular dependencies + known_imports[path.hash()] = "." + + var raw_lines: PackedStringArray = text.split("\n") + + for id in range(0, raw_lines.size()): + var line = raw_lines[id] + if is_import_line(line): + var import_data: Dictionary = extract_import_path_and_name(line) + + if not import_data.has("path"): continue + + var import_hash: int = import_data.path.hash() + if import_data.size() > 0: + # Keep track of titles so we can add imported ones later + if str(import_hash) in _imported_titles.keys(): + add_error(id, 0, DMConstants.ERR_FILE_ALREADY_IMPORTED) + if import_data.prefix in _imported_titles.values(): + add_error(id, 0, DMConstants.ERR_DUPLICATE_IMPORT_NAME) + _imported_titles[str(import_hash)] = import_data.prefix + + # Import the file content + if not known_imports.has(import_hash): + var error: Error = import_content(import_data.path, import_data.prefix, _imported_line_map, known_imports) + if error != OK: + add_error(id, 0, error) + + # Make a map so we can refer compiled lines to where they were imported from + if not _imported_line_map.has(import_hash): + _imported_line_map[import_hash] = { + hash = import_hash, + imported_on_line_number = id, + from_line = 0, + to_line = 0 + } + + var imported_content: String = "" + var cummulative_line_number: int = 0 + for item in _imported_line_map.values(): + item["from_line"] = cummulative_line_number + if known_imports.has(item.hash): + cummulative_line_number += known_imports[item.hash].split("\n").size() + item["to_line"] = cummulative_line_number + if known_imports.has(item.hash): + imported_content += known_imports[item.hash] + "\n" + + if imported_content == "": + _imported_line_count = 0 + return text.split("\n") + else: + _imported_line_count = cummulative_line_number + 1 + # Combine imported lines with the original lines + return (imported_content + "\n" + text).split("\n") + + +## Import content from another dialogue file or return an ERR +func import_content(path: String, prefix: String, imported_line_map: Dictionary, known_imports: Dictionary) -> Error: + if FileAccess.file_exists(path): + var file = FileAccess.open(path, FileAccess.READ) + var content: PackedStringArray = file.get_as_text().strip_edges().split("\n") + + for index in range(0, content.size()): + var line = content[index] + if is_import_line(line): + var import = extract_import_path_and_name(line) + if import.size() > 0: + if not known_imports.has(import.path.hash()): + # Add an empty record into the keys just so we don't end up with cyclic dependencies + known_imports[import.path.hash()] = "" + if import_content(import.path, import.prefix, imported_line_map, known_imports) != OK: + return ERR_LINK_FAILED + + if not imported_line_map.has(import.path.hash()): + # Make a map so we can refer compiled lines to where they were imported from + imported_line_map[import.path.hash()] = { + hash = import.path.hash(), + imported_on_line_number = index, + from_line = 0, + to_line = 0 + } + + _imported_titles[import.prefix] = import.path.hash() + + var origin_hash: int = -1 + for hash_value in known_imports.keys(): + if known_imports[hash_value] == ".": + origin_hash = hash_value + + # Replace any titles or jump points with references to the files they point to (event if they point to their own file) + for i in range(0, content.size()): + var line = content[i] + if line.strip_edges().begins_with("~ "): + var title = line.strip_edges().substr(2) + if "/" in line: + var bits = title.split("/") + content[i] = "~ %s/%s" % [_imported_titles[bits[0]], bits[1]] + else: + content[i] = "~ %s/%s" % [str(path.hash()), title] + + elif "=>< " in line: + var jump: String = line.substr(line.find("=>< ") + "=>< ".length()).strip_edges() + if "/" in jump: + var bits: PackedStringArray = jump.split("/") + var title_hash: int = _imported_titles[bits[0]] + if title_hash == origin_hash: + content[i] = "%s=>< %s" % [line.split("=>< ")[0], bits[1]] + else: + content[i] = "%s=>< %s/%s" % [line.split("=>< ")[0], title_hash, bits[1]] + + elif not jump in ["END", "END!"]: + content[i] = "%s=>< %s/%s" % [line.split("=>< ")[0], str(path.hash()), jump] + + elif "=> " in line: + var jump: String = line.substr(line.find("=> ") + "=> ".length()).strip_edges() + if "/" in jump: + var bits: PackedStringArray = jump.split("/") + var title_hash: int = _imported_titles[bits[0]] + if title_hash == origin_hash: + content[i] = "%s=> %s" % [line.split("=> ")[0], bits[1]] + else: + content[i] = "%s=> %s/%s" % [line.split("=> ")[0], title_hash, bits[1]] + + elif not jump in ["END", "END!"]: + content[i] = "%s=> %s/%s" % [line.split("=> ")[0], str(path.hash()), jump] + + imported_paths.append(path) + known_imports[path.hash()] = "\n".join(content) + "\n=> END\n" + return OK + else: + return ERR_FILE_NOT_FOUND + + +## Build a tree of parent/child relationships +func build_line_tree(raw_lines: PackedStringArray) -> DMTreeLine: + var root: DMTreeLine = DMTreeLine.new("") + var parent_chain: Array[DMTreeLine] = [root] + var previous_line: DMTreeLine + var doc_comments: PackedStringArray = [] + + # Get list of known autoloads + var autoload_names: PackedStringArray = get_autoload_names() + + for i in range(0, raw_lines.size()): + var raw_line: String = raw_lines[i] + var tree_line: DMTreeLine = DMTreeLine.new(str(i - _imported_line_count)) + + tree_line.line_number = i + 1 + tree_line.type = get_line_type(raw_line) + tree_line.text = raw_line.strip_edges() + + # Handle any "using" directives. + if raw_line.begins_with("using "): + var using_match: RegExMatch = regex.USING_REGEX.search(raw_line) + if "state" in using_match.names: + var using_state: String = using_match.strings[using_match.names.state].strip_edges() + if not using_state in autoload_names: + add_error(i, 0, DMConstants.ERR_UNKNOWN_USING) + elif not using_state in using_states: + using_states.append(using_state) + continue + # Ignore import lines because they've already been processed. + elif is_import_line(raw_line): + continue + + tree_line.indent = get_indent(raw_line) + + # Attach doc comments + if raw_line.strip_edges().begins_with("##"): + doc_comments.append(raw_line.replace("##", "").strip_edges()) + elif tree_line.type == DMConstants.TYPE_DIALOGUE: + tree_line.notes = "\n".join(doc_comments) + doc_comments.clear() + + # Empty lines are only kept so that we can work out groupings of things (eg. responses and + # randomised lines). Therefore we only need to keep one empty line in a row even if there + # are multiple. The indent of an empty line is assumed to be the same as the non-empty line + # following it. That way, grouping calculations should work. + if tree_line.type in [DMConstants.TYPE_UNKNOWN, DMConstants.TYPE_COMMENT] and raw_lines.size() > i + 1: + var next_line = raw_lines[i + 1] + if previous_line and previous_line.type in [DMConstants.TYPE_UNKNOWN, DMConstants.TYPE_COMMENT] and tree_line.type in [DMConstants.TYPE_UNKNOWN, DMConstants.TYPE_COMMENT]: + continue + else: + tree_line.type = DMConstants.TYPE_UNKNOWN + tree_line.indent = get_indent(next_line) + + # Check for indentation changes + if tree_line.indent > parent_chain.size() - 1: + parent_chain.append(previous_line) + elif tree_line.indent < parent_chain.size() - 1: + parent_chain.resize(tree_line.indent + 1) + + # Add any titles to the list of known titles + if tree_line.type == DMConstants.TYPE_TITLE: + var title: String = tree_line.text.substr(2) + if title == "": + add_error(i, 2, DMConstants.ERR_EMPTY_TITLE) + elif titles.has(title): + add_error(i, 2, DMConstants.ERR_DUPLICATE_TITLE) + else: + titles[title] = tree_line.id + if "/" in title: + # Replace the hash title with something human readable. + var bits: PackedStringArray = title.split("/") + if _imported_titles.has(bits[0]): + title = _imported_titles[bits[0]] + "/" + bits[1] + titles[title] = tree_line.id + elif first_title == "" and i >= _imported_line_count: + first_title = tree_line.id + + # Append the current line to the current parent (note: the root is the most basic parent). + var parent: DMTreeLine = parent_chain[parent_chain.size() - 1] + tree_line.parent = weakref(parent) + parent.children.append(tree_line) + + previous_line = tree_line + + return root + + +#endregion + +#region Parsing + + +func parse_line_tree(root: DMTreeLine, parent: DMCompiledLine = null) -> Array[DMCompiledLine]: + var compiled_lines: Array[DMCompiledLine] = [] + + for i in range(0, root.children.size()): + var tree_line: DMTreeLine = root.children[i] + var line: DMCompiledLine = DMCompiledLine.new(tree_line.id, tree_line.type) + + match line.type: + DMConstants.TYPE_UNKNOWN: + line.next_id = get_next_matching_sibling_id(root.children, i, parent, _first) + + DMConstants.TYPE_TITLE: + parse_title_line(tree_line, line, root.children, i, parent) + + DMConstants.TYPE_CONDITION: + parse_condition_line(tree_line, line, root.children, i, parent) + + DMConstants.TYPE_WHILE: + parse_while_line(tree_line, line, root.children, i, parent) + + DMConstants.TYPE_MATCH: + parse_match_line(tree_line, line, root.children, i, parent) + + DMConstants.TYPE_WHEN: + parse_when_line(tree_line, line, root.children, i, parent) + + DMConstants.TYPE_MUTATION: + parse_mutation_line(tree_line, line, root.children, i, parent) + + DMConstants.TYPE_GOTO: + # Extract any weighted random calls before parsing dialogue + if tree_line.text.begins_with("%"): + parse_random_line(tree_line, line, root.children, i, parent) + parse_goto_line(tree_line, line, root.children, i, parent) + + DMConstants.TYPE_RESPONSE: + parse_response_line(tree_line, line, root.children, i, parent) + + DMConstants.TYPE_RANDOM: + parse_random_line(tree_line, line, root.children, i, parent) + + DMConstants.TYPE_DIALOGUE: + # Extract any weighted random calls before parsing dialogue + if tree_line.text.begins_with("%"): + parse_random_line(tree_line, line, root.children, i, parent) + parse_dialogue_line(tree_line, line, root.children, i, parent) + + # Main line map is keyed by ID + lines[line.id] = line + + # Returned lines order is preserved so that it can be used for compiling children + compiled_lines.append(line) + + return compiled_lines + + +## Parse a title and apply it to the given line +func parse_title_line(tree_line: DMTreeLine, line: DMCompiledLine, siblings: Array[DMTreeLine], sibling_index: int, parent: DMCompiledLine) -> Error: + var result: Error = OK + + line.text = tree_line.text.substr(tree_line.text.find("~ ") + 2).strip_edges() + + # Titles can't have numbers as the first letter (unless they are external titles which get replaced with hashes) + if tree_line.line_number >= _imported_line_count and regex.BEGINS_WITH_NUMBER_REGEX.search(line.text): + result = add_error(tree_line.line_number, 2, DMConstants.ERR_TITLE_BEGINS_WITH_NUMBER) + + # Only import titles are allowed to have "/" in them + var valid_title = regex.VALID_TITLE_REGEX.search(line.text.replace("/", "")) + if not valid_title: + result = add_error(tree_line.line_number, 2, DMConstants.ERR_TITLE_INVALID_CHARACTERS) + + line.next_id = get_next_matching_sibling_id(siblings, sibling_index, parent, _first) + + ## Update the titles reference to point to the actual first line + titles[line.text] = line.next_id + + ## Update any lines that point to this title + if _goto_lines.has(line.text): + for goto_line in _goto_lines[line.text]: + goto_line.next_id = line.next_id + + return result + + +## Parse a goto and apply it to the given line. +func parse_goto_line(tree_line: DMTreeLine, line: DMCompiledLine, siblings: Array[DMTreeLine], sibling_index: int, parent: DMCompiledLine) -> Error: + # Work out where this line is jumping to. + var goto_data: DMResolvedGotoData = DMResolvedGotoData.new(tree_line.text, titles) + if goto_data.error: + return add_error(tree_line.line_number, tree_line.indent + 2, goto_data.error) + if goto_data.next_id or goto_data.expression: + line.next_id = goto_data.next_id + line.next_id_expression = goto_data.expression + add_reference_to_title(goto_data.title, line) + + if goto_data.is_snippet: + line.is_snippet = true + line.next_id_after = get_next_matching_sibling_id(siblings, sibling_index, parent, _first) + + return OK + + +## Parse a condition line and apply to the given line +func parse_condition_line(tree_line: DMTreeLine, line: DMCompiledLine, siblings: Array[DMTreeLine], sibling_index: int, parent: DMCompiledLine) -> Error: + # Work out the next IDs before parsing the condition line itself so that the last + # child can inherit from the chain. + + # Find the next conditional sibling that is part of this grouping (if there is one). + for next_sibling: DMTreeLine in siblings.slice(sibling_index + 1): + if not next_sibling.type in [DMConstants.TYPE_UNKNOWN, DMConstants.TYPE_CONDITION]: + break + elif next_sibling.type == DMConstants.TYPE_CONDITION: + if next_sibling.text.begins_with("el"): + line.next_sibling_id = next_sibling.id + break + else: + break + + line.next_id_after = get_next_matching_sibling_id(siblings, sibling_index, parent, func(s: DMTreeLine): + # The next line that isn't a conditional or is a new "if" + return s.type != DMConstants.TYPE_CONDITION or s.text.begins_with("if ") + ) + # Any empty IDs should end the conversation. + if line.next_id_after == DMConstants.ID_NULL: + line.next_id_after = parent.next_id_after if parent != null and parent.next_id_after else DMConstants.ID_END + + # Having no nested body is an immediate failure. + if tree_line.children.size() == 0: + return add_error(tree_line.line_number, tree_line.indent, DMConstants.ERR_INVALID_CONDITION_INDENTATION) + + # Try to parse the conditional expression ("else" has no expression). + if "if " in tree_line.text: + var condition: Dictionary = extract_condition(tree_line.text, false, tree_line.indent) + if condition.has("error"): + return add_error(tree_line.line_number, condition.index, condition.error) + else: + line.expression = condition + + # Parse any nested body lines + parse_children(tree_line, line) + + return OK + + +## Parse a while loop and apply it to the given line. +func parse_while_line(tree_line: DMTreeLine, line: DMCompiledLine, siblings: Array[DMTreeLine], sibling_index: int, parent: DMCompiledLine) -> Error: + line.next_id_after = get_next_matching_sibling_id(siblings, sibling_index, parent, _first) + + # Parse the while condition + var condition: Dictionary = extract_condition(tree_line.text, false, tree_line.indent) + if condition.has("error"): + return add_error(tree_line.line_number, condition.index, condition.error) + else: + line.expression = condition + + # Parse the nested body (it should take care of looping back to this line when it finishes) + parse_children(tree_line, line) + + return OK + + +func parse_match_line(tree_line: DMTreeLine, line: DMCompiledLine, siblings: Array[DMTreeLine], sibling_index: int, parent: DMCompiledLine) -> Error: + var result: Error = OK + + # The next line after is the next sibling + line.next_id_after = get_next_matching_sibling_id(siblings, sibling_index, parent, _first) + + # Extract the condition to match to + var condition: Dictionary = extract_condition(tree_line.text, false, tree_line.indent) + if condition.has("error"): + result = add_error(tree_line.line_number, condition.index, condition.error) + else: + line.expression = condition + + # Match statements should have children + if tree_line.children.size() == 0: + result = add_error(tree_line.line_number, tree_line.indent, DMConstants.ERR_INVALID_CONDITION_INDENTATION) + + # Check that all children are when or else. + for child in tree_line.children: + if child.type == DMConstants.TYPE_WHEN: continue + if child.type == DMConstants.TYPE_CONDITION and child.text.begins_with("else"): continue + + result = add_error(child.line_number, child.indent, DMConstants.ERR_EXPECTED_WHEN_OR_ELSE) + + # Each child should be a "when" or "else". We don't need those lines themselves, just their + # condition and the line they point to if the conditions passes. + var children: Array[DMCompiledLine] = parse_children(tree_line, line) + for child: DMCompiledLine in children: + # "when" cases + if child.type == DMConstants.TYPE_WHEN: + line.siblings.append({ + condition = child.expression, + next_id = child.next_id + }) + # "else" case + elif child.type == DMConstants.TYPE_CONDITION: + if line.siblings.any(func(s): return s.has("is_else")): + result = add_error(child.line_number, child.indent, DMConstants.ERR_ONLY_ONE_ELSE_ALLOWED) + else: + line.siblings.append({ + next_id = child.next_id, + is_else = true + }) + # Remove the line from the list of all lines because we don't need it any more. + lines.erase(child.id) + + return result + + +func parse_when_line(tree_line: DMTreeLine, line: DMCompiledLine, siblings: Array[DMTreeLine], sibling_index: int, parent: DMCompiledLine) -> Error: + var result: Error = OK + + # This when line should be found inside a match line + if parent.type != DMConstants.TYPE_MATCH: + result = add_error(tree_line.line_number, tree_line.indent, DMConstants.ERR_WHEN_MUST_BELONG_TO_MATCH) + + # When lines should have children + if tree_line.children.size() == 0: + result = add_error(tree_line.line_number, tree_line.indent, DMConstants.ERR_INVALID_CONDITION_INDENTATION) + + # The next line after a when is the same as its parent match line + line.next_id_after = parent.next_id_after + + # Extract the condition to match to + var condition: Dictionary = extract_condition(tree_line.text, false, tree_line.indent) + if condition.has("error"): + result = add_error(tree_line.line_number, condition.index, condition.error) + else: + line.expression = condition + + parse_children(tree_line, line) + + return result + + +## Parse a mutation line and apply it to the given line +func parse_mutation_line(tree_line: DMTreeLine, line: DMCompiledLine, siblings: Array[DMTreeLine], sibling_index: int, parent: DMCompiledLine) -> Error: + var mutation: Dictionary = extract_mutation(tree_line.text) + if mutation.has("error"): + return add_error(tree_line.line_number, mutation.index, mutation.error) + else: + line.expression = mutation + + line.next_id = get_next_matching_sibling_id(siblings, sibling_index, parent, _first) + + return OK + + +## Parse a response and apply it to the given line. +func parse_response_line(tree_line: DMTreeLine, line: DMCompiledLine, siblings: Array[DMTreeLine], sibling_index: int, parent: DMCompiledLine) -> Error: + var result: Error = OK + + # Remove the "- " + tree_line.text = tree_line.text.substr(2) + + # Extract the static line ID + var static_line_id: String = extract_static_line_id(tree_line.text) + if static_line_id: + tree_line.text = tree_line.text.replace("[ID:%s]" % [static_line_id], "") + line.translation_key = static_line_id + + # Handle conditional responses and remove them from the prompt text. + if " [if " in tree_line.text: + var condition = extract_condition(tree_line.text, true, tree_line.indent) + if condition.has("error"): + result = add_error(tree_line.line_number, condition.index, condition.error) + else: + line.expression = condition + tree_line.text = regex.WRAPPED_CONDITION_REGEX.sub(tree_line.text, "").strip_edges() + + # Find the original response in this group of responses. + var original_response: DMTreeLine = tree_line + for i in range(sibling_index - 1, 0, -1): + if siblings[i].type == DMConstants.TYPE_RESPONSE: + original_response = siblings[i] + elif siblings[i].type != DMConstants.TYPE_UNKNOWN: + break + + # If it's the original response then set up an original line. + if original_response == tree_line: + line.next_id_after = get_next_matching_sibling_id(siblings, sibling_index, parent, (func(s: DMTreeLine): + # The next line that isn't a response. + return not s.type in [DMConstants.TYPE_RESPONSE, DMConstants.TYPE_UNKNOWN] + ), true) + line.responses = [line.id] + # If this line has children then the next ID is the first child. + if tree_line.children.size() > 0: + parse_children(tree_line, line) + # Otherwise use the same ID for after the random group. + else: + line.next_id = line.next_id_after + # Otherwise let the original line know about it. + else: + var original_line: DMCompiledLine = lines[original_response.id] + line.next_id_after = original_line.next_id_after + line.siblings = original_line.siblings + original_line.responses.append(line.id) + # If this line has children then the next ID is the first child. + if tree_line.children.size() > 0: + parse_children(tree_line, line) + # Otherwise use the original line's next ID after. + else: + line.next_id = original_line.next_id_after + + parse_character_and_dialogue(tree_line, line, siblings, sibling_index, parent) + + return OK + + +## Parse a randomised line +func parse_random_line(tree_line: DMTreeLine, line: DMCompiledLine, siblings: Array[DMTreeLine], sibling_index: int, parent: DMCompiledLine) -> Error: + # Find the weight + var weight: float = 1 + var found = regex.WEIGHTED_RANDOM_SIBLINGS_REGEX.search(tree_line.text + " ") + var condition: Dictionary = {} + if found: + if found.names.has("weight"): + weight = found.strings[found.names.weight].to_float() + if found.names.has("condition"): + condition = extract_condition(tree_line.text, true, tree_line.indent) + + # Find the original random sibling. It will be the jump off point. + var original_sibling: DMTreeLine = tree_line + for i in range(sibling_index - 1, -1, -1): + if siblings[i] and siblings[i].is_random: + original_sibling = siblings[i] + else: + break + + var weighted_sibling: Dictionary = { weight = weight, id = line.id, condition = condition } + + # If it's the original sibling then set up an original line. + if original_sibling == tree_line: + line.next_id_after = get_next_matching_sibling_id(siblings, sibling_index, parent, (func(s: DMTreeLine): + # The next line that isn't a randomised line. + # NOTE: DMTreeLine.is_random won't be set at this point so we need to check for the "%" prefix. + return not s.text.begins_with("%") + ), true) + line.siblings = [weighted_sibling] + # If this line has children then the next ID is the first child. + if tree_line.children.size() > 0: + parse_children(tree_line, line) + # Otherwise use the same ID for after the random group. + else: + line.next_id = line.next_id_after + + # Otherwise let the original line know about it. + else: + var original_line: DMCompiledLine = lines[original_sibling.id] + line.next_id_after = original_line.next_id_after + line.siblings = original_line.siblings + original_line.siblings.append(weighted_sibling) + # If this line has children then the next ID is the first child. + if tree_line.children.size() > 0: + parse_children(tree_line, line) + # Otherwise use the original line's next ID after. + else: + line.next_id = original_line.next_id_after + + # Remove the randomise syntax from the line. + tree_line.text = regex.WEIGHTED_RANDOM_SIBLINGS_REGEX.sub(tree_line.text, "") + tree_line.is_random = true + + return OK + + +## Parse some dialogue and apply it to the given line. +func parse_dialogue_line(tree_line: DMTreeLine, line: DMCompiledLine, siblings: Array[DMTreeLine], sibling_index: int, parent: DMCompiledLine) -> Error: + var result: Error = OK + + # Remove escape character + if tree_line.text.begins_with("\\using"): tree_line.text = tree_line.text.substr(1) + if tree_line.text.begins_with("\\if"): tree_line.text = tree_line.text.substr(1) + if tree_line.text.begins_with("\\elif"): tree_line.text = tree_line.text.substr(1) + if tree_line.text.begins_with("\\else"): tree_line.text = tree_line.text.substr(1) + if tree_line.text.begins_with("\\while"): tree_line.text = tree_line.text.substr(1) + if tree_line.text.begins_with("\\match"): tree_line.text = tree_line.text.substr(1) + if tree_line.text.begins_with("\\when"): tree_line.text = tree_line.text.substr(1) + if tree_line.text.begins_with("\\do"): tree_line.text = tree_line.text.substr(1) + if tree_line.text.begins_with("\\set"): tree_line.text = tree_line.text.substr(1) + if tree_line.text.begins_with("\\-"): tree_line.text = tree_line.text.substr(1) + if tree_line.text.begins_with("\\~"): tree_line.text = tree_line.text.substr(1) + if tree_line.text.begins_with("\\=>"): tree_line.text = tree_line.text.substr(1) + if tree_line.text.begins_with("\\%"): tree_line.text = tree_line.text.substr(1) + + # Append any further dialogue + for i in range(0, tree_line.children.size()): + var child: DMTreeLine = tree_line.children[i] + if child.type == DMConstants.TYPE_DIALOGUE: + tree_line.text += "\n" + child.text + else: + result = add_error(child.line_number, child.indent, DMConstants.ERR_INVALID_INDENTATION) + + # Extract the static line ID + var static_line_id: String = extract_static_line_id(tree_line.text) + if static_line_id: + tree_line.text = tree_line.text.replace("[ID:%s]" % [static_line_id], "") + line.translation_key = static_line_id + + # Check for simultaneous lines + if tree_line.text.begins_with("| "): + # Jumps are only allowed on the origin line. + if " =>" in tree_line.text: + result = add_error(tree_line.line_number, tree_line.indent, DMConstants.ERR_GOTO_NOT_ALLOWED_ON_CONCURRECT_LINES) + # Check for a valid previous line. + tree_line.text = tree_line.text.substr(2) + var previous_sibling: DMTreeLine = siblings[sibling_index - 1] + if previous_sibling.type != DMConstants.TYPE_DIALOGUE: + result = add_error(tree_line.line_number, tree_line.indent, DMConstants.ERR_CONCURRENT_LINE_WITHOUT_ORIGIN) + else: + # Because the previous line's concurrent_lines array is the same as + # any line before that this doesn't need to check any higher up. + var previous_line: DMCompiledLine = lines[previous_sibling.id] + previous_line.concurrent_lines.append(line.id) + line.concurrent_lines = previous_line.concurrent_lines + + parse_character_and_dialogue(tree_line, line, siblings, sibling_index, parent) + + # Check for any inline expression errors + var resolved_line_data: DMResolvedLineData = DMResolvedLineData.new("") + var bbcodes: Array[Dictionary] = resolved_line_data.find_bbcode_positions_in_string(tree_line.text, true, true) + for bbcode: Dictionary in bbcodes: + var tag: String = bbcode.code + var code: String = bbcode.raw_args + if tag.begins_with("do") or tag.begins_with("set") or tag.begins_with("if"): + var expression: Array = expression_parser.tokenise(code, DMConstants.TYPE_MUTATION, bbcode.start + bbcode.code.length()) + if expression.size() == 0: + add_error(tree_line.line_number, tree_line.indent, DMConstants.ERR_INVALID_EXPRESSION) + elif expression[0].type == DMConstants.TYPE_ERROR: + add_error(tree_line.line_number, tree_line.indent + expression[0].index, expression[0].value) + + # If the line isn't part of a weighted random group then make it point to the next + # available sibling. + if line.next_id == DMConstants.ID_NULL and line.siblings.size() == 0: + line.next_id = get_next_matching_sibling_id(siblings, sibling_index, parent, func(s: DMTreeLine): + # Ignore concurrent lines. + return not s.text.begins_with("| ") + ) + + return result + + +## Parse the character name and dialogue and apply it to a given line. +func parse_character_and_dialogue(tree_line: DMTreeLine, line: DMCompiledLine, siblings: Array[DMTreeLine], sibling_index: int, parent: DMCompiledLine) -> Error: + var result: Error = OK + + var text: String = tree_line.text + + # Attach any doc comments. + line.notes = tree_line.notes + + # Extract tags. + var tag_data: DMResolvedTagData = DMResolvedTagData.new(text) + line.tags = tag_data.tags + text = tag_data.text_without_tags + + # Handle inline gotos and remove them from the prompt text. + if " =><" in text: + # Because of when the return point needs to be known at runtime we need to split + # this line into two (otherwise the return point would be dependent on the balloon). + var goto_data: DMResolvedGotoData = DMResolvedGotoData.new(text, titles) + if goto_data.error: + result = add_error(tree_line.line_number, tree_line.indent + 3, goto_data.error) + if goto_data.next_id or goto_data.expression: + text = goto_data.text_without_goto + var goto_line: DMCompiledLine = DMCompiledLine.new(line.id + ".1", DMConstants.TYPE_GOTO) + goto_line.next_id = goto_data.next_id + line.next_id_expression = goto_data.expression + if line.type == DMConstants.TYPE_RESPONSE: + goto_line.next_id_after = get_next_matching_sibling_id(siblings, sibling_index, parent, func(s: DMTreeLine): + # If this is coming from a response then we want the next non-response line. + return s.type != DMConstants.TYPE_RESPONSE + ) + else: + goto_line.next_id_after = get_next_matching_sibling_id(siblings, sibling_index, parent, _first) + goto_line.is_snippet = true + lines[goto_line.id] = goto_line + line.next_id = goto_line.id + add_reference_to_title(goto_data.title, goto_line) + elif " =>" in text: + var goto_data: DMResolvedGotoData = DMResolvedGotoData.new(text, titles) + if goto_data.error: + result = add_error(tree_line.line_number, tree_line.indent + 2, goto_data.error) + if goto_data.next_id or goto_data.expression: + text = goto_data.text_without_goto + line.next_id = goto_data.next_id + line.next_id_expression = goto_data.expression + add_reference_to_title(goto_data.title, line) + + # Handle the dialogue. + text = text.replace("\\:", "!ESCAPED_COLON!") + if ": " in text: + # If a character was given then split it out. + var bits = Array(text.strip_edges().split(": ")) + line.character = bits.pop_front().strip_edges() + if not line.character in character_names: + character_names.append(line["character"]) + # Character names can have expressions in them. + line.character_replacements = expression_parser.extract_replacements(line.character, tree_line.indent) + for replacement in line.character_replacements: + if replacement.has("error"): + result = add_error(tree_line.line_number, replacement.index, replacement.error) + text = ": ".join(bits).replace("!ESCAPED_COLON!", ":") + else: + line.character = "" + text = text.replace("!ESCAPED_COLON!", ":") + + # Extract any expressions in the dialogue. + line.text_replacements = expression_parser.extract_replacements(text, line.character.length() + 2 + tree_line.indent) + for replacement in line.text_replacements: + if replacement.has("error"): + result = add_error(tree_line.line_number, replacement.index, replacement.error) + + # Replace any newlines. + text = text.replace("\\n", "\n").strip_edges() + + # If there was no manual translation key then just use the text itself + if line.translation_key == "": + line.translation_key = text + + line.text = text + + # IDs can't be duplicated for text that doesn't match. + if line.translation_key != "": + if _known_translation_keys.has(line.translation_key) and _known_translation_keys.get(line.translation_key) != line.text: + result = add_error(tree_line.line_number, tree_line.indent, DMConstants.ERR_DUPLICATE_ID) + else: + _known_translation_keys[line.translation_key] = line.text + # Show an error if missing translations is enabled + elif DMSettings.get_setting(DMSettings.MISSING_TRANSLATIONS_ARE_ERRORS, false): + result = add_error(tree_line.line_number, tree_line.indent, DMConstants.ERR_MISSING_ID) + + return result + + +#endregion + +#region Errors + + +## Add a compilation error to the list. Returns the given error code. +func add_error(line_number: int, column_number: int, error: int) -> Error: + # See if the error was in an imported file + for item in _imported_line_map.values(): + if line_number < item.to_line: + errors.append({ + line_number = item.imported_on_line_number, + column_number = 0, + error = DMConstants.ERR_ERRORS_IN_IMPORTED_FILE, + external_error = error, + external_line_number = line_number + }) + return error + + # Otherwise, it's in this file + errors.append({ + line_number = line_number - _imported_line_count, + column_number = column_number, + error = error + }) + + return error + + +#endregion + +#region Helpers + + +## Get the names of any autoloads in the project. +func get_autoload_names() -> PackedStringArray: + var autoloads: PackedStringArray = [] + + var project = ConfigFile.new() + project.load("res://project.godot") + if project.has_section("autoload"): + return Array(project.get_section_keys("autoload")).filter(func(key): return key != "DialogueManager") + + return autoloads + + +## Check if a line is importing another file. +func is_import_line(text: String) -> bool: + return text.begins_with("import ") and " as " in text + + +## Extract the import information from an import line +func extract_import_path_and_name(line: String) -> Dictionary: + var found: RegExMatch = regex.IMPORT_REGEX.search(line) + if found: + return { + path = found.strings[found.names.path], + prefix = found.strings[found.names.prefix] + } + else: + return {} + + +## Get the indent of a raw line +func get_indent(raw_line: String) -> int: + var tabs: RegExMatch = regex.INDENT_REGEX.search(raw_line) + if tabs: + return tabs.get_string().length() + else: + return 0 + + +## Get the type of a raw line +func get_line_type(raw_line: String) -> String: + raw_line = raw_line.strip_edges() + var text: String = regex.WEIGHTED_RANDOM_SIBLINGS_REGEX.sub(raw_line + " ", "").strip_edges() + + if text.begins_with("import "): + return DMConstants.TYPE_IMPORT + + if text.begins_with("#"): + return DMConstants.TYPE_COMMENT + + if text.begins_with("~ "): + return DMConstants.TYPE_TITLE + + if text.begins_with("if ") or text.begins_with("elif") or text.begins_with("else"): + return DMConstants.TYPE_CONDITION + + if text.begins_with("while "): + return DMConstants.TYPE_WHILE + + if text.begins_with("match "): + return DMConstants.TYPE_MATCH + + if text.begins_with("when "): + return DMConstants.TYPE_WHEN + + if text.begins_with("do ") or text.begins_with("do! ") or text.begins_with("set "): + return DMConstants.TYPE_MUTATION + + if text.begins_with("=> ") or text.begins_with("=>< "): + return DMConstants.TYPE_GOTO + + if text.begins_with("- "): + return DMConstants.TYPE_RESPONSE + + if raw_line.begins_with("%") and text.is_empty(): + return DMConstants.TYPE_RANDOM + + if not text.is_empty(): + return DMConstants.TYPE_DIALOGUE + + return DMConstants.TYPE_UNKNOWN + + +## Get the next sibling that passes a [Callable] matcher. +func get_next_matching_sibling_id(siblings: Array[DMTreeLine], from_index: int, parent: DMCompiledLine, matcher: Callable, with_empty_lines: bool = false) -> String: + for i in range(from_index + 1, siblings.size()): + var next_sibling: DMTreeLine = siblings[i] + + if not with_empty_lines: + # Ignore empty lines + if not next_sibling or next_sibling.type == DMConstants.TYPE_UNKNOWN: + continue + + if matcher.call(next_sibling): + return next_sibling.id + + # If no next ID can be found then check the parent for where to go next. + if parent != null: + return parent.id if parent.type == DMConstants.TYPE_WHILE else parent.next_id_after + + return DMConstants.ID_NULL + + +## Extract a static line ID from some text. +func extract_static_line_id(text: String) -> String: + # Find a static translation key, eg. [ID:something] + var found: RegExMatch = regex.STATIC_LINE_ID_REGEX.search(text) + if found: + return found.strings[found.names.id] + else: + return "" + + +## Extract a condition (or inline condition) from some text. +func extract_condition(text: String, is_wrapped: bool, index: int) -> Dictionary: + var regex: RegEx = regex.WRAPPED_CONDITION_REGEX if is_wrapped else regex.CONDITION_REGEX + var found: RegExMatch = regex.search(text) + + if found == null: + return { + index = 0, + error = DMConstants.ERR_INCOMPLETE_EXPRESSION + } + + var raw_condition: String = found.strings[found.names.expression] + if raw_condition.ends_with(":"): + raw_condition = raw_condition.substr(0, raw_condition.length() - 1) + + var expression: Array = expression_parser.tokenise(raw_condition, DMConstants.TYPE_CONDITION, index + found.get_start("expression")) + + if expression.size() == 0: + return { + index = index + found.get_start("expression"), + error = DMConstants.ERR_INCOMPLETE_EXPRESSION + } + elif expression[0].type == DMConstants.TYPE_ERROR: + return { + index = expression[0].index, + error = expression[0].value + } + else: + return { + expression = expression + } + + +## Extract a mutation from some text. +func extract_mutation(text: String) -> Dictionary: + var found: RegExMatch = regex.MUTATION_REGEX.search(text) + + if not found: + return { + index = 0, + error = DMConstants.ERR_INCOMPLETE_EXPRESSION + } + + if found.names.has("expression"): + var expression: Array = expression_parser.tokenise(found.strings[found.names.expression], DMConstants.TYPE_MUTATION, found.get_start("expression")) + if expression.size() == 0: + return { + index = found.get_start("expression"), + error = DMConstants.ERR_INCOMPLETE_EXPRESSION + } + elif expression[0].type == DMConstants.TYPE_ERROR: + return { + index = expression[0].index, + error = expression[0].value + } + else: + return { + expression = expression, + is_blocking = not "!" in found.strings[found.names.keyword] + } + + else: + return { + index = found.get_start(), + error = DMConstants.ERR_INCOMPLETE_EXPRESSION + } + + +## Keep track of lines referencing titles because their own next_id might not have been resolved yet. +func add_reference_to_title(title: String, line: DMCompiledLine) -> void: + if title in [DMConstants.ID_END, DMConstants.ID_END_CONVERSATION, DMConstants.ID_NULL]: return + + if not _goto_lines.has(title): + _goto_lines[title] = [] + _goto_lines[title].append(line) + + +## Parse a nested block of child lines +func parse_children(tree_line: DMTreeLine, line: DMCompiledLine) -> Array[DMCompiledLine]: + var children = parse_line_tree(tree_line, line) + if children.size() > 0: + line.next_id = children.front().id + # The last child should jump to the next line after its parent condition group + var last_child: DMCompiledLine = children.back() + if last_child.next_id == DMConstants.ID_NULL: + last_child.next_id = line.next_id_after + if last_child.siblings.size() > 0: + for sibling in last_child.siblings: + lines.get(sibling.id).next_id = last_child.next_id + + return children + + +#endregion diff --git a/addons/dialogue_manager/compiler/compilation.gd.uid b/addons/dialogue_manager/compiler/compilation.gd.uid new file mode 100644 index 00000000..24a13eeb --- /dev/null +++ b/addons/dialogue_manager/compiler/compilation.gd.uid @@ -0,0 +1 @@ +uid://dsgpnyqg6cprg diff --git a/addons/dialogue_manager/compiler/compiled_line.gd b/addons/dialogue_manager/compiler/compiled_line.gd new file mode 100644 index 00000000..972fd5cb --- /dev/null +++ b/addons/dialogue_manager/compiler/compiled_line.gd @@ -0,0 +1,157 @@ +## A compiled line of dialogue. +class_name DMCompiledLine extends RefCounted + + +## The ID of the line +var id: String +## The translation key (or static line ID). +var translation_key: String = "" +## The type of line. +var type: String = "" +## The character name. +var character: String = "" +## Any interpolation expressions for the character name. +var character_replacements: Array[Dictionary] = [] +## The text of the line. +var text: String = "" +## Any interpolation expressions for the text. +var text_replacements: Array[Dictionary] = [] +## Any response siblings associated with this line. +var responses: PackedStringArray = [] +## Any randomise or case siblings for this line. +var siblings: Array[Dictionary] = [] +## Any lines said simultaneously. +var concurrent_lines: PackedStringArray = [] +## Any tags on this line. +var tags: PackedStringArray = [] +## The condition or mutation expression for this line. +var expression: Dictionary = {} +## The next sequential line to go to after this line. +var next_id: String = "" +## The next line to go to after this line if it is unknown and compile time. +var next_id_expression: Array[Dictionary] = [] +## Whether this jump line should return after the jump target sequence has ended. +var is_snippet: bool = false +## The ID of the next sibling line. +var next_sibling_id: String = "" +## The ID after this line if it belongs to a block (eg. conditions). +var next_id_after: String = "" +## Any doc comments attached to this line. +var notes: String = "" + + +#region Hooks + + +func _init(initial_id: String, initial_type: String) -> void: + id = initial_id + type = initial_type + + +func _to_string() -> String: + var s: Array = [ + "[%s]" % [type], + "%s:" % [character] if character != "" else null, + text if text != "" else null, + expression if expression.size() > 0 else null, + "[%s]" % [",".join(tags)] if tags.size() > 0 else null, + str(siblings) if siblings.size() > 0 else null, + str(responses) if responses.size() > 0 else null, + "=> END" if "end" in next_id else "=> %s" % [next_id], + "(~> %s)" % [next_sibling_id] if next_sibling_id != "" else null, + "(==> %s)" % [next_id_after] if next_id_after != "" else null, + ].filter(func(item): return item != null) + + return " ".join(s) + + +#endregion + +#region Helpers + + +## Express this line as a [Dictionary] that can be stored in a resource. +func to_data() -> Dictionary: + var d: Dictionary = { + id = id, + type = type, + next_id = next_id + } + + if next_id_expression.size() > 0: + d.next_id_expression = next_id_expression + + match type: + DMConstants.TYPE_CONDITION: + d.condition = expression + if not next_sibling_id.is_empty(): + d.next_sibling_id = next_sibling_id + d.next_id_after = next_id_after + + DMConstants.TYPE_WHILE: + d.condition = expression + d.next_id_after = next_id_after + + DMConstants.TYPE_MATCH: + d.condition = expression + d.next_id_after = next_id_after + d.cases = siblings + + DMConstants.TYPE_MUTATION: + d.mutation = expression + + DMConstants.TYPE_GOTO: + d.is_snippet = is_snippet + d.next_id_after = next_id_after + if not siblings.is_empty(): + d.siblings = siblings + + DMConstants.TYPE_RANDOM: + d.siblings = siblings + + DMConstants.TYPE_RESPONSE: + d.text = text + + if not responses.is_empty(): + d.responses = responses + + if translation_key != text: + d.translation_key = translation_key + if not expression.is_empty(): + d.condition = expression + if not character.is_empty(): + d.character = character + if not character_replacements.is_empty(): + d.character_replacements = character_replacements + if not text_replacements.is_empty(): + d.text_replacements = text_replacements + if not tags.is_empty(): + d.tags = tags + if not notes.is_empty(): + d.notes = notes + + DMConstants.TYPE_DIALOGUE: + d.text = text + + if translation_key != text: + d.translation_key = translation_key + + if not character.is_empty(): + d.character = character + if not character_replacements.is_empty(): + d.character_replacements = character_replacements + if not text_replacements.is_empty(): + d.text_replacements = text_replacements + if not tags.is_empty(): + d.tags = tags + if not notes.is_empty(): + d.notes = notes + if not siblings.is_empty(): + d.siblings = siblings + if not concurrent_lines.is_empty(): + d.concurrent_lines = concurrent_lines + + return d + + +#endregion diff --git a/addons/dialogue_manager/compiler/compiled_line.gd.uid b/addons/dialogue_manager/compiler/compiled_line.gd.uid new file mode 100644 index 00000000..17ec55e9 --- /dev/null +++ b/addons/dialogue_manager/compiler/compiled_line.gd.uid @@ -0,0 +1 @@ +uid://dg8j5hudp4210 diff --git a/addons/dialogue_manager/compiler/compiler.gd b/addons/dialogue_manager/compiler/compiler.gd new file mode 100644 index 00000000..a370ef6a --- /dev/null +++ b/addons/dialogue_manager/compiler/compiler.gd @@ -0,0 +1,51 @@ +## A compiler of Dialogue Manager dialogue. +class_name DMCompiler extends RefCounted + + +## Compile a dialogue script. +static func compile_string(text: String, path: String) -> DMCompilerResult: + var compilation: DMCompilation = DMCompilation.new() + compilation.compile(text, path) + + var result: DMCompilerResult = DMCompilerResult.new() + result.imported_paths = compilation.imported_paths + result.using_states = compilation.using_states + result.character_names = compilation.character_names + result.titles = compilation.titles + result.first_title = compilation.first_title + result.errors = compilation.errors + result.lines = compilation.data + result.raw_text = text + + return result + + +## Get the line type of a string. The returned string will match one of the [code]TYPE_[/code] constants of [DMConstants]. +static func get_line_type(text: String) -> String: + var compilation: DMCompilation = DMCompilation.new() + return compilation.get_line_type(text) + + +## Get the static line ID (eg. [code][ID:SOMETHING][/code]) of some text. +static func get_static_line_id(text: String) -> String: + var compilation: DMCompilation = DMCompilation.new() + return compilation.extract_static_line_id(text) + + +## Get the translatable part of a line. +static func extract_translatable_string(text: String) -> String: + var compilation: DMCompilation = DMCompilation.new() + + var tree_line = DMTreeLine.new("") + tree_line.text = text + var line: DMCompiledLine = DMCompiledLine.new("", compilation.get_line_type(text)) + compilation.parse_character_and_dialogue(tree_line, line, [tree_line], 0, null) + + return line.text + + +## Get the known titles in a dialogue script. +static func get_titles_in_text(text: String, path: String) -> Dictionary: + var compilation: DMCompilation = DMCompilation.new() + compilation.build_line_tree(compilation.inject_imported_files(text, path)) + return compilation.titles diff --git a/addons/dialogue_manager/compiler/compiler.gd.uid b/addons/dialogue_manager/compiler/compiler.gd.uid new file mode 100644 index 00000000..e041f104 --- /dev/null +++ b/addons/dialogue_manager/compiler/compiler.gd.uid @@ -0,0 +1 @@ +uid://chtfdmr0cqtp4 diff --git a/addons/dialogue_manager/compiler/compiler_regex.gd b/addons/dialogue_manager/compiler/compiler_regex.gd new file mode 100644 index 00000000..ead998ba --- /dev/null +++ b/addons/dialogue_manager/compiler/compiler_regex.gd @@ -0,0 +1,49 @@ +## A collection of [RegEx] for use by the [DMCompiler]. +class_name DMCompilerRegEx extends RefCounted + + +var IMPORT_REGEX: RegEx = RegEx.create_from_string("import \"(?[^\"]+)\" as (?[a-zA-Z_\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]+)") +var USING_REGEX: RegEx = RegEx.create_from_string("^using (?.*)$") +var INDENT_REGEX: RegEx = RegEx.create_from_string("^\\t+") +var VALID_TITLE_REGEX: RegEx = RegEx.create_from_string("^[a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]*$") +var BEGINS_WITH_NUMBER_REGEX: RegEx = RegEx.create_from_string("^\\d") +var CONDITION_REGEX: RegEx = RegEx.create_from_string("(if|elif|while|else if|match|when) (?.*)\\:?") +var WRAPPED_CONDITION_REGEX: RegEx = RegEx.create_from_string("\\[if (?.*)\\]") +var MUTATION_REGEX: RegEx = RegEx.create_from_string("(?do|do!|set) (?.*)") +var STATIC_LINE_ID_REGEX: RegEx = RegEx.create_from_string("\\[ID:(?.*?)\\]") +var WEIGHTED_RANDOM_SIBLINGS_REGEX: RegEx = RegEx.create_from_string("^\\%(?[\\d.]+)?( \\[if (?.+?)\\])? ") +var GOTO_REGEX: RegEx = RegEx.create_from_string("=>.*)") + +var INLINE_RANDOM_REGEX: RegEx = RegEx.create_from_string("\\[\\[(?.*?)\\]\\]") +var INLINE_CONDITIONALS_REGEX: RegEx = RegEx.create_from_string("\\[if (?.+?)\\](?.*?)\\[\\/if\\]") + +var TAGS_REGEX: RegEx = RegEx.create_from_string("\\[#(?.*?)\\]") + +var REPLACEMENTS_REGEX: RegEx = RegEx.create_from_string("{{(.*?)}}") + +var ALPHA_NUMERIC: RegEx = RegEx.create_from_string("[^a-zA-Z0-9\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]+") + +var TOKEN_DEFINITIONS: Dictionary = { + DMConstants.TOKEN_FUNCTION: RegEx.create_from_string("^[a-zA-Z_\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]*\\("), + DMConstants.TOKEN_DICTIONARY_REFERENCE: RegEx.create_from_string("^[a-zA-Z_\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]*\\["), + DMConstants.TOKEN_PARENS_OPEN: RegEx.create_from_string("^\\("), + DMConstants.TOKEN_PARENS_CLOSE: RegEx.create_from_string("^\\)"), + DMConstants.TOKEN_BRACKET_OPEN: RegEx.create_from_string("^\\["), + DMConstants.TOKEN_BRACKET_CLOSE: RegEx.create_from_string("^\\]"), + DMConstants.TOKEN_BRACE_OPEN: RegEx.create_from_string("^\\{"), + DMConstants.TOKEN_BRACE_CLOSE: RegEx.create_from_string("^\\}"), + DMConstants.TOKEN_COLON: RegEx.create_from_string("^:"), + DMConstants.TOKEN_COMPARISON: RegEx.create_from_string("^(==|<=|>=|<|>|!=|in )"), + DMConstants.TOKEN_ASSIGNMENT: RegEx.create_from_string("^(\\+=|\\-=|\\*=|/=|=)"), + DMConstants.TOKEN_NUMBER: RegEx.create_from_string("^\\-?\\d+(\\.\\d+)?"), + DMConstants.TOKEN_OPERATOR: RegEx.create_from_string("^(\\+|\\-|\\*|/|%)"), + DMConstants.TOKEN_COMMA: RegEx.create_from_string("^,"), + DMConstants.TOKEN_DOT: RegEx.create_from_string("^\\."), + DMConstants.TOKEN_STRING: RegEx.create_from_string("^&?(\".*?\"|\'.*?\')"), + DMConstants.TOKEN_NOT: RegEx.create_from_string("^(not( |$)|!)"), + DMConstants.TOKEN_AND_OR: RegEx.create_from_string("^(and|or|&&|\\|\\|)( |$)"), + DMConstants.TOKEN_VARIABLE: RegEx.create_from_string("^[a-zA-Z_\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]*"), + DMConstants.TOKEN_COMMENT: RegEx.create_from_string("^#.*"), + DMConstants.TOKEN_CONDITION: RegEx.create_from_string("^(if|elif|else)"), + DMConstants.TOKEN_BOOL: RegEx.create_from_string("^(true|false)") +} diff --git a/addons/dialogue_manager/compiler/compiler_regex.gd.uid b/addons/dialogue_manager/compiler/compiler_regex.gd.uid new file mode 100644 index 00000000..bd969dfa --- /dev/null +++ b/addons/dialogue_manager/compiler/compiler_regex.gd.uid @@ -0,0 +1 @@ +uid://d3tvcrnicjibp diff --git a/addons/dialogue_manager/compiler/compiler_result.gd b/addons/dialogue_manager/compiler/compiler_result.gd new file mode 100644 index 00000000..acbf60f7 --- /dev/null +++ b/addons/dialogue_manager/compiler/compiler_result.gd @@ -0,0 +1,27 @@ +## The result of using the [DMCompiler] to compile some dialogue. +class_name DMCompilerResult extends RefCounted + + +## Any paths that were imported into the compiled dialogue file. +var imported_paths: PackedStringArray = [] + +## Any "using" directives. +var using_states: PackedStringArray = [] + +## All titles in the file and the line they point to. +var titles: Dictionary = {} + +## The first title in the file. +var first_title: String = "" + +## All character names. +var character_names: PackedStringArray = [] + +## Any compilation errors. +var errors: Array[Dictionary] = [] + +## A map of all compiled lines. +var lines: Dictionary = {} + +## The raw dialogue text. +var raw_text: String = "" diff --git a/addons/dialogue_manager/compiler/compiler_result.gd.uid b/addons/dialogue_manager/compiler/compiler_result.gd.uid new file mode 100644 index 00000000..f1f76fd0 --- /dev/null +++ b/addons/dialogue_manager/compiler/compiler_result.gd.uid @@ -0,0 +1 @@ +uid://dmk74tknimqvg diff --git a/addons/dialogue_manager/compiler/expression_parser.gd b/addons/dialogue_manager/compiler/expression_parser.gd new file mode 100644 index 00000000..384340fc --- /dev/null +++ b/addons/dialogue_manager/compiler/expression_parser.gd @@ -0,0 +1,497 @@ +## A class for parsing a condition/mutation expression for use with the [DMCompiler]. +class_name DMExpressionParser extends RefCounted + + +# Reference to the common [RegEx] that the parser needs. +var regex: DMCompilerRegEx = DMCompilerRegEx.new() + + +## Break a string down into an expression. +func tokenise(text: String, line_type: String, index: int) -> Array: + var tokens: Array[Dictionary] = [] + var limit: int = 0 + while text.strip_edges() != "" and limit < 1000: + limit += 1 + var found = _find_match(text) + if found.size() > 0: + tokens.append({ + index = index, + type = found.type, + value = found.value + }) + index += found.value.length() + text = found.remaining_text + elif text.begins_with(" "): + index += 1 + text = text.substr(1) + else: + return _build_token_tree_error(DMConstants.ERR_INVALID_EXPRESSION, index) + + return _build_token_tree(tokens, line_type, "")[0] + + +## Extract any expressions from some text +func extract_replacements(text: String, index: int) -> Array[Dictionary]: + var founds: Array[RegExMatch] = regex.REPLACEMENTS_REGEX.search_all(text) + + if founds == null or founds.size() == 0: + return [] + + var replacements: Array[Dictionary] = [] + for found in founds: + var replacement: Dictionary = {} + var value_in_text: String = found.strings[0].substr(0, found.strings[0].length() - 2).substr(2) + + # If there are closing curlie hard-up against the end of a {{...}} block then check for further + # curlies just outside of the block. + var text_suffix: String = text.substr(found.get_end(0)) + var expression_suffix: String = "" + while text_suffix.begins_with("}"): + expression_suffix += "}" + text_suffix = text_suffix.substr(1) + value_in_text += expression_suffix + + var expression: Array = tokenise(value_in_text, DMConstants.TYPE_DIALOGUE, index + found.get_start(1)) + if expression.size() == 0: + replacement = { + index = index + found.get_start(1), + error = DMConstants.ERR_INCOMPLETE_EXPRESSION + } + elif expression[0].type == DMConstants.TYPE_ERROR: + replacement = { + index = expression[0].index, + error = expression[0].value + } + else: + replacement = { + value_in_text = "{{%s}}" % value_in_text, + expression = expression + } + replacements.append(replacement) + + return replacements + + +#region Helpers + + +# Create a token that represents an error. +func _build_token_tree_error(error: int, index: int) -> Array: + return [{ type = DMConstants.TOKEN_ERROR, value = error, index = index }] + + +# Convert a list of tokens into an abstract syntax tree. +func _build_token_tree(tokens: Array[Dictionary], line_type: String, expected_close_token: String) -> Array: + var tree: Array[Dictionary] = [] + var limit = 0 + while tokens.size() > 0 and limit < 1000: + limit += 1 + var token = tokens.pop_front() + + var error = _check_next_token(token, tokens, line_type, expected_close_token) + if error != OK: + var error_token: Dictionary = tokens[1] if tokens.size() > 1 else token + return [_build_token_tree_error(error, error_token.index), tokens] + + match token.type: + DMConstants.TOKEN_FUNCTION: + var sub_tree = _build_token_tree(tokens, line_type, DMConstants.TOKEN_PARENS_CLOSE) + + if sub_tree[0].size() > 0 and sub_tree[0][0].type == DMConstants.TOKEN_ERROR: + return [_build_token_tree_error(sub_tree[0][0].value, sub_tree[0][0].index), tokens] + + tree.append({ + type = DMConstants.TOKEN_FUNCTION, + # Consume the trailing "(" + function = token.value.substr(0, token.value.length() - 1), + value = _tokens_to_list(sub_tree[0]), + i = token.index + }) + tokens = sub_tree[1] + + DMConstants.TOKEN_DICTIONARY_REFERENCE: + var sub_tree = _build_token_tree(tokens, line_type, DMConstants.TOKEN_BRACKET_CLOSE) + + if sub_tree[0].size() > 0 and sub_tree[0][0].type == DMConstants.TOKEN_ERROR: + return [_build_token_tree_error(sub_tree[0][0].value, sub_tree[0][0].index), tokens] + + var args = _tokens_to_list(sub_tree[0]) + if args.size() != 1: + return [_build_token_tree_error(DMConstants.ERR_INVALID_INDEX, token.index), tokens] + + tree.append({ + type = DMConstants.TOKEN_DICTIONARY_REFERENCE, + # Consume the trailing "[" + variable = token.value.substr(0, token.value.length() - 1), + value = args[0], + i = token.index + }) + tokens = sub_tree[1] + + DMConstants.TOKEN_BRACE_OPEN: + var sub_tree = _build_token_tree(tokens, line_type, DMConstants.TOKEN_BRACE_CLOSE) + + if sub_tree[0].size() > 0 and sub_tree[0][0].type == DMConstants.TOKEN_ERROR: + return [_build_token_tree_error(sub_tree[0][0].value, sub_tree[0][0].index), tokens] + + var t = sub_tree[0] + for i in range(0, t.size() - 2): + # Convert Lua style dictionaries to string keys + if t[i].type == DMConstants.TOKEN_VARIABLE and t[i+1].type == DMConstants.TOKEN_ASSIGNMENT: + t[i].type = DMConstants.TOKEN_STRING + t[i+1].type = DMConstants.TOKEN_COLON + t[i+1].erase("value") + + tree.append({ + type = DMConstants.TOKEN_DICTIONARY, + value = _tokens_to_dictionary(sub_tree[0]), + i = token.index + }) + + tokens = sub_tree[1] + + DMConstants.TOKEN_BRACKET_OPEN: + var sub_tree = _build_token_tree(tokens, line_type, DMConstants.TOKEN_BRACKET_CLOSE) + + if sub_tree[0].size() > 0 and sub_tree[0][0].type == DMConstants.TOKEN_ERROR: + return [_build_token_tree_error(sub_tree[0][0].value, sub_tree[0][0].index), tokens] + + var type = DMConstants.TOKEN_ARRAY + var value = _tokens_to_list(sub_tree[0]) + + # See if this is referencing a nested dictionary value + if tree.size() > 0: + var previous_token = tree[tree.size() - 1] + if previous_token.type in [DMConstants.TOKEN_DICTIONARY_REFERENCE, DMConstants.TOKEN_DICTIONARY_NESTED_REFERENCE]: + type = DMConstants.TOKEN_DICTIONARY_NESTED_REFERENCE + value = value[0] + + tree.append({ + type = type, + value = value, + i = token.index + }) + tokens = sub_tree[1] + + DMConstants.TOKEN_PARENS_OPEN: + var sub_tree = _build_token_tree(tokens, line_type, DMConstants.TOKEN_PARENS_CLOSE) + + if sub_tree[0].size() > 0 and sub_tree[0][0].type == DMConstants.TOKEN_ERROR: + return [_build_token_tree_error(sub_tree[0][0].value, sub_tree[0][0].index), tokens] + + tree.append({ + type = DMConstants.TOKEN_GROUP, + value = sub_tree[0], + i = token.index + }) + tokens = sub_tree[1] + + DMConstants.TOKEN_PARENS_CLOSE, \ + DMConstants.TOKEN_BRACE_CLOSE, \ + DMConstants.TOKEN_BRACKET_CLOSE: + if token.type != expected_close_token: + return [_build_token_tree_error(DMConstants.ERR_UNEXPECTED_CLOSING_BRACKET, token.index), tokens] + + tree.append({ + type = token.type, + i = token.index + }) + + return [tree, tokens] + + DMConstants.TOKEN_NOT: + # Double nots negate each other + if tokens.size() > 0 and tokens.front().type == DMConstants.TOKEN_NOT: + tokens.pop_front() + else: + tree.append({ + type = token.type, + i = token.index + }) + + DMConstants.TOKEN_COMMA, \ + DMConstants.TOKEN_COLON, \ + DMConstants.TOKEN_DOT: + tree.append({ + type = token.type, + i = token.index + }) + + DMConstants.TOKEN_COMPARISON, \ + DMConstants.TOKEN_ASSIGNMENT, \ + DMConstants.TOKEN_OPERATOR, \ + DMConstants.TOKEN_AND_OR, \ + DMConstants.TOKEN_VARIABLE: + var value = token.value.strip_edges() + if value == "&&": + value = "and" + elif value == "||": + value = "or" + tree.append({ + type = token.type, + value = value, + i = token.index + }) + + DMConstants.TOKEN_STRING: + if token.value.begins_with("&"): + tree.append({ + type = token.type, + value = StringName(token.value.substr(2, token.value.length() - 3)), + i = token.index + }) + else: + tree.append({ + type = token.type, + value = token.value.substr(1, token.value.length() - 2), + i = token.index + }) + + DMConstants.TOKEN_CONDITION: + return [_build_token_tree_error(DMConstants.ERR_UNEXPECTED_CONDITION, token.index), token] + + DMConstants.TOKEN_BOOL: + tree.append({ + type = token.type, + value = token.value.to_lower() == "true", + i = token.index + }) + + DMConstants.TOKEN_NUMBER: + var value = token.value.to_float() if "." in token.value else token.value.to_int() + # If previous token is a number and this one is a negative number then + # inject a minus operator token in between them. + if tree.size() > 0 and token.value.begins_with("-") and tree[tree.size() - 1].type == DMConstants.TOKEN_NUMBER: + tree.append(({ + type = DMConstants.TOKEN_OPERATOR, + value = "-", + i = token.index + })) + tree.append({ + type = token.type, + value = -1 * value, + i = token.index + }) + else: + tree.append({ + type = token.type, + value = value, + i = token.index + }) + + if expected_close_token != "": + var index: int = tokens[0].index if tokens.size() > 0 else 0 + return [_build_token_tree_error(DMConstants.ERR_MISSING_CLOSING_BRACKET, index), tokens] + + return [tree, tokens] + + +# Check the next token to see if it is valid to follow this one. +func _check_next_token(token: Dictionary, next_tokens: Array[Dictionary], line_type: String, expected_close_token: String) -> Error: + var next_token: Dictionary = { type = null } + if next_tokens.size() > 0: + next_token = next_tokens.front() + + # Guard for assigning in a condition. If the assignment token isn't inside a Lua dictionary + # then it's an unexpected assignment in a condition line. + if token.type == DMConstants.TOKEN_ASSIGNMENT and line_type == DMConstants.TYPE_CONDITION and not next_tokens.any(func(t): return t.type == expected_close_token): + return DMConstants.ERR_UNEXPECTED_ASSIGNMENT + + # Special case for a negative number after this one + if token.type == DMConstants.TOKEN_NUMBER and next_token.type == DMConstants.TOKEN_NUMBER and next_token.value.begins_with("-"): + return OK + + var expected_token_types = [] + var unexpected_token_types = [] + match token.type: + DMConstants.TOKEN_FUNCTION, \ + DMConstants.TOKEN_PARENS_OPEN: + unexpected_token_types = [ + null, + DMConstants.TOKEN_COMMA, + DMConstants.TOKEN_COLON, + DMConstants.TOKEN_COMPARISON, + DMConstants.TOKEN_ASSIGNMENT, + DMConstants.TOKEN_OPERATOR, + DMConstants.TOKEN_AND_OR, + DMConstants.TOKEN_DOT + ] + + DMConstants.TOKEN_BRACKET_CLOSE: + unexpected_token_types = [ + DMConstants.TOKEN_NOT, + DMConstants.TOKEN_BOOL, + DMConstants.TOKEN_STRING, + DMConstants.TOKEN_NUMBER, + DMConstants.TOKEN_VARIABLE + ] + + DMConstants.TOKEN_BRACE_OPEN: + expected_token_types = [ + DMConstants.TOKEN_STRING, + DMConstants.TOKEN_VARIABLE, + DMConstants.TOKEN_NUMBER, + DMConstants.TOKEN_BRACE_CLOSE + ] + + DMConstants.TOKEN_PARENS_CLOSE, \ + DMConstants.TOKEN_BRACE_CLOSE: + unexpected_token_types = [ + DMConstants.TOKEN_NOT, + DMConstants.TOKEN_ASSIGNMENT, + DMConstants.TOKEN_BOOL, + DMConstants.TOKEN_STRING, + DMConstants.TOKEN_NUMBER, + DMConstants.TOKEN_VARIABLE + ] + + DMConstants.TOKEN_COMPARISON, \ + DMConstants.TOKEN_OPERATOR, \ + DMConstants.TOKEN_COMMA, \ + DMConstants.TOKEN_DOT, \ + DMConstants.TOKEN_NOT, \ + DMConstants.TOKEN_AND_OR, \ + DMConstants.TOKEN_DICTIONARY_REFERENCE: + unexpected_token_types = [ + null, + DMConstants.TOKEN_COMMA, + DMConstants.TOKEN_COLON, + DMConstants.TOKEN_COMPARISON, + DMConstants.TOKEN_ASSIGNMENT, + DMConstants.TOKEN_OPERATOR, + DMConstants.TOKEN_AND_OR, + DMConstants.TOKEN_PARENS_CLOSE, + DMConstants.TOKEN_BRACE_CLOSE, + DMConstants.TOKEN_BRACKET_CLOSE, + DMConstants.TOKEN_DOT + ] + + DMConstants.TOKEN_COLON: + unexpected_token_types = [ + DMConstants.TOKEN_COMMA, + DMConstants.TOKEN_COLON, + DMConstants.TOKEN_COMPARISON, + DMConstants.TOKEN_ASSIGNMENT, + DMConstants.TOKEN_OPERATOR, + DMConstants.TOKEN_AND_OR, + DMConstants.TOKEN_PARENS_CLOSE, + DMConstants.TOKEN_BRACE_CLOSE, + DMConstants.TOKEN_BRACKET_CLOSE, + DMConstants.TOKEN_DOT + ] + + DMConstants.TOKEN_BOOL, \ + DMConstants.TOKEN_STRING, \ + DMConstants.TOKEN_NUMBER: + unexpected_token_types = [ + DMConstants.TOKEN_NOT, + DMConstants.TOKEN_ASSIGNMENT, + DMConstants.TOKEN_BOOL, + DMConstants.TOKEN_STRING, + DMConstants.TOKEN_NUMBER, + DMConstants.TOKEN_VARIABLE, + DMConstants.TOKEN_FUNCTION, + DMConstants.TOKEN_PARENS_OPEN, + DMConstants.TOKEN_BRACE_OPEN, + DMConstants.TOKEN_BRACKET_OPEN + ] + + DMConstants.TOKEN_VARIABLE: + unexpected_token_types = [ + DMConstants.TOKEN_NOT, + DMConstants.TOKEN_BOOL, + DMConstants.TOKEN_STRING, + DMConstants.TOKEN_NUMBER, + DMConstants.TOKEN_VARIABLE, + DMConstants.TOKEN_FUNCTION, + DMConstants.TOKEN_PARENS_OPEN, + DMConstants.TOKEN_BRACE_OPEN, + DMConstants.TOKEN_BRACKET_OPEN + ] + + if (expected_token_types.size() > 0 and not next_token.type in expected_token_types or unexpected_token_types.size() > 0 and next_token.type in unexpected_token_types): + match next_token.type: + null: + return DMConstants.ERR_UNEXPECTED_END_OF_EXPRESSION + + DMConstants.TOKEN_FUNCTION: + return DMConstants.ERR_UNEXPECTED_FUNCTION + + DMConstants.TOKEN_PARENS_OPEN, \ + DMConstants.TOKEN_PARENS_CLOSE: + return DMConstants.ERR_UNEXPECTED_BRACKET + + DMConstants.TOKEN_COMPARISON, \ + DMConstants.TOKEN_ASSIGNMENT, \ + DMConstants.TOKEN_OPERATOR, \ + DMConstants.TOKEN_NOT, \ + DMConstants.TOKEN_AND_OR: + return DMConstants.ERR_UNEXPECTED_OPERATOR + + DMConstants.TOKEN_COMMA: + return DMConstants.ERR_UNEXPECTED_COMMA + DMConstants.TOKEN_COLON: + return DMConstants.ERR_UNEXPECTED_COLON + DMConstants.TOKEN_DOT: + return DMConstants.ERR_UNEXPECTED_DOT + + DMConstants.TOKEN_BOOL: + return DMConstants.ERR_UNEXPECTED_BOOLEAN + DMConstants.TOKEN_STRING: + return DMConstants.ERR_UNEXPECTED_STRING + DMConstants.TOKEN_NUMBER: + return DMConstants.ERR_UNEXPECTED_NUMBER + DMConstants.TOKEN_VARIABLE: + return DMConstants.ERR_UNEXPECTED_VARIABLE + + return DMConstants.ERR_INVALID_EXPRESSION + + return OK + + +# Convert a series of comma separated tokens to an [Array]. +func _tokens_to_list(tokens: Array[Dictionary]) -> Array[Array]: + var list: Array[Array] = [] + var current_item: Array[Dictionary] = [] + for token in tokens: + if token.type == DMConstants.TOKEN_COMMA: + list.append(current_item) + current_item = [] + else: + current_item.append(token) + + if current_item.size() > 0: + list.append(current_item) + + return list + + +# Convert a series of key/value tokens into a [Dictionary] +func _tokens_to_dictionary(tokens: Array[Dictionary]) -> Dictionary: + var dictionary = {} + for i in range(0, tokens.size()): + if tokens[i].type == DMConstants.TOKEN_COLON: + if tokens.size() == i + 2: + dictionary[tokens[i - 1]] = tokens[i + 1] + else: + dictionary[tokens[i - 1]] = { type = DMConstants.TOKEN_GROUP, value = tokens.slice(i + 1), i = tokens[0].i } + + return dictionary + + +# Work out what the next token is from a string. +func _find_match(input: String) -> Dictionary: + for key in regex.TOKEN_DEFINITIONS.keys(): + var regex = regex.TOKEN_DEFINITIONS.get(key) + var found = regex.search(input) + if found: + return { + type = key, + remaining_text = input.substr(found.strings[0].length()), + value = found.strings[0] + } + + return {} + + +#endregion diff --git a/addons/dialogue_manager/compiler/expression_parser.gd.uid b/addons/dialogue_manager/compiler/expression_parser.gd.uid new file mode 100644 index 00000000..0793701f --- /dev/null +++ b/addons/dialogue_manager/compiler/expression_parser.gd.uid @@ -0,0 +1 @@ +uid://dbi4hbar8ubwu diff --git a/addons/dialogue_manager/compiler/resolved_goto_data.gd b/addons/dialogue_manager/compiler/resolved_goto_data.gd new file mode 100644 index 00000000..16bca6f5 --- /dev/null +++ b/addons/dialogue_manager/compiler/resolved_goto_data.gd @@ -0,0 +1,68 @@ +## Data associated with a dialogue jump/goto line. +class_name DMResolvedGotoData extends RefCounted + + +## The title that was specified +var title: String = "" +## The target line's ID +var next_id: String = "" +## An expression to determine the target line at runtime. +var expression: Array[Dictionary] = [] +## The given line text with the jump syntax removed. +var text_without_goto: String = "" +## Whether this is a jump-and-return style jump. +var is_snippet: bool = false +## A parse error if there was one. +var error: int +## The index in the string where +var index: int = 0 + +# An instance of the compiler [RegEx] list. +var regex: DMCompilerRegEx = DMCompilerRegEx.new() + + +func _init(text: String, titles: Dictionary) -> void: + if not "=> " in text and not "=>< " in text: return + + if "=> " in text: + text_without_goto = text.substr(0, text.find("=> ")).strip_edges() + elif "=>< " in text: + is_snippet = true + text_without_goto = text.substr(0, text.find("=>< ")).strip_edges() + + var found: RegExMatch = regex.GOTO_REGEX.search(text) + if found == null: + return + + title = found.strings[found.names.goto].strip_edges() + index = found.get_start(0) + + if title == "": + error = DMConstants.ERR_UNKNOWN_TITLE + return + + # "=> END!" means end the conversation, ignoring any "=><" chains. + if title == "END!": + next_id = DMConstants.ID_END_CONVERSATION + + # "=> END" means end the current title (and go back to the previous one if there is one + # in the stack) + elif title == "END": + next_id = DMConstants.ID_END + + elif titles.has(title): + next_id = titles.get(title) + elif title.begins_with("{{"): + var expression_parser: DMExpressionParser = DMExpressionParser.new() + var title_expression: Array[Dictionary] = expression_parser.extract_replacements(title, 0) + if title_expression[0].has("error"): + error = title_expression[0].error + else: + expression = title_expression[0].expression + else: + next_id = title + error = DMConstants.ERR_UNKNOWN_TITLE + + +func _to_string() -> String: + return "%s =>%s %s (%s)" % [text_without_goto, "<" if is_snippet else "", title, next_id] diff --git a/addons/dialogue_manager/compiler/resolved_goto_data.gd.uid b/addons/dialogue_manager/compiler/resolved_goto_data.gd.uid new file mode 100644 index 00000000..cb05e08b --- /dev/null +++ b/addons/dialogue_manager/compiler/resolved_goto_data.gd.uid @@ -0,0 +1 @@ +uid://llhl5pt47eoq diff --git a/addons/dialogue_manager/compiler/resolved_line_data.gd b/addons/dialogue_manager/compiler/resolved_line_data.gd new file mode 100644 index 00000000..1d1a7167 --- /dev/null +++ b/addons/dialogue_manager/compiler/resolved_line_data.gd @@ -0,0 +1,167 @@ +## Any data associated with inline dialogue BBCodes. +class_name DMResolvedLineData extends RefCounted + +## The line's text +var text: String = "" +## A map of pauses against where they are found in the text. +var pauses: Dictionary = {} +## A map of speed changes against where they are found in the text. +var speeds: Dictionary = {} +## A list of any mutations to run and where they are found in the text. +var mutations: Array[Array] = [] +## A duration reference for the line. Represented as "auto" or a stringified number. +var time: String = "" + + +func _init(line: String) -> void: + text = line + pauses = {} + speeds = {} + mutations = [] + time = "" + + var bbcodes: Array = [] + + # Remove any escaped brackets (ie. "\[") + var escaped_open_brackets: PackedInt32Array = [] + var escaped_close_brackets: PackedInt32Array = [] + for i in range(0, text.length() - 1): + if text.substr(i, 2) == "\\[": + text = text.substr(0, i) + "!" + text.substr(i + 2) + escaped_open_brackets.append(i) + elif text.substr(i, 2) == "\\]": + text = text.substr(0, i) + "!" + text.substr(i + 2) + escaped_close_brackets.append(i) + + # Extract all of the BB codes so that we know the actual text (we could do this easier with + # a RichTextLabel but then we'd need to await idle_frame which is annoying) + var bbcode_positions = find_bbcode_positions_in_string(text) + var accumulaive_length_offset = 0 + for position in bbcode_positions: + # Ignore our own markers + if position.code in ["wait", "speed", "/speed", "do", "do!", "set", "next", "if", "else", "/if"]: + continue + + bbcodes.append({ + bbcode = position.bbcode, + start = position.start, + offset_start = position.start - accumulaive_length_offset + }) + accumulaive_length_offset += position.bbcode.length() + + for bb in bbcodes: + text = text.substr(0, bb.offset_start) + text.substr(bb.offset_start + bb.bbcode.length()) + + # Now find any dialogue markers + var next_bbcode_position = find_bbcode_positions_in_string(text, false) + var limit = 0 + while next_bbcode_position.size() > 0 and limit < 1000: + limit += 1 + + var bbcode = next_bbcode_position[0] + + var index = bbcode.start + var code = bbcode.code + var raw_args = bbcode.raw_args + var args = {} + if code in ["do", "do!", "set"]: + var compilation: DMCompilation = DMCompilation.new() + args["value"] = compilation.extract_mutation("%s %s" % [code, raw_args]) + else: + # Could be something like: + # "=1.0" + # " rate=20 level=10" + if raw_args and raw_args[0] == "=": + raw_args = "value" + raw_args + for pair in raw_args.strip_edges().split(" "): + if "=" in pair: + var bits = pair.split("=") + args[bits[0]] = bits[1] + + match code: + "wait": + if pauses.has(index): + pauses[index] += args.get("value").to_float() + else: + pauses[index] = args.get("value").to_float() + "speed": + speeds[index] = args.get("value").to_float() + "/speed": + speeds[index] = 1.0 + "do", "do!", "set": + mutations.append([index, args.get("value")]) + "next": + time = args.get("value") if args.has("value") else "0" + + # Find any BB codes that are after this index and remove the length from their start + var length = bbcode.bbcode.length() + for bb in bbcodes: + if bb.offset_start > bbcode.start: + bb.offset_start -= length + bb.start -= length + + # Find any escaped brackets after this that need moving + for i in range(0, escaped_open_brackets.size()): + if escaped_open_brackets[i] > bbcode.start: + escaped_open_brackets[i] -= length + for i in range(0, escaped_close_brackets.size()): + if escaped_close_brackets[i] > bbcode.start: + escaped_close_brackets[i] -= length + + text = text.substr(0, index) + text.substr(index + length) + next_bbcode_position = find_bbcode_positions_in_string(text, false) + + # Put the BB Codes back in + for bb in bbcodes: + text = text.insert(bb.start, bb.bbcode) + + # Put the escaped brackets back in + for index in escaped_open_brackets: + text = text.left(index) + "[" + text.right(text.length() - index - 1) + for index in escaped_close_brackets: + text = text.left(index) + "]" + text.right(text.length() - index - 1) + + +func find_bbcode_positions_in_string(string: String, find_all: bool = true, include_conditions: bool = false) -> Array[Dictionary]: + if not "[" in string: return [] + + var positions: Array[Dictionary] = [] + + var open_brace_count: int = 0 + var start: int = 0 + var bbcode: String = "" + var code: String = "" + var is_finished_code: bool = false + for i in range(0, string.length()): + if string[i] == "[": + if open_brace_count == 0: + start = i + bbcode = "" + code = "" + is_finished_code = false + open_brace_count += 1 + + else: + if not is_finished_code and (string[i].to_upper() != string[i] or string[i] == "/" or string[i] == "!"): + code += string[i] + else: + is_finished_code = true + + if open_brace_count > 0: + bbcode += string[i] + + if string[i] == "]": + open_brace_count -= 1 + if open_brace_count == 0 and (include_conditions or not code in ["if", "else", "/if"]): + positions.append({ + bbcode = bbcode, + code = code, + start = start, + end = i, + raw_args = bbcode.substr(code.length() + 1, bbcode.length() - code.length() - 2).strip_edges() + }) + + if not find_all: + return positions + + return positions diff --git a/addons/dialogue_manager/compiler/resolved_line_data.gd.uid b/addons/dialogue_manager/compiler/resolved_line_data.gd.uid new file mode 100644 index 00000000..bbea7d26 --- /dev/null +++ b/addons/dialogue_manager/compiler/resolved_line_data.gd.uid @@ -0,0 +1 @@ +uid://0k6q8kukq0qa diff --git a/addons/dialogue_manager/compiler/resolved_tag_data.gd b/addons/dialogue_manager/compiler/resolved_tag_data.gd new file mode 100644 index 00000000..e926adae --- /dev/null +++ b/addons/dialogue_manager/compiler/resolved_tag_data.gd @@ -0,0 +1,26 @@ +## Tag data associated with a line of dialogue. +class_name DMResolvedTagData extends RefCounted + + +## The list of tags. +var tags: PackedStringArray = [] +## The line with any tag syntax removed. +var text_without_tags: String = "" + +# An instance of the compiler [RegEx]. +var regex: DMCompilerRegEx = DMCompilerRegEx.new() + + +func _init(text: String) -> void: + var resolved_tags: PackedStringArray = [] + var tag_matches: Array[RegExMatch] = regex.TAGS_REGEX.search_all(text) + for tag_match in tag_matches: + text = text.replace(tag_match.get_string(), "") + var tags = tag_match.get_string().replace("[#", "").replace("]", "").replace(", ", ",").split(",") + for tag in tags: + tag = tag.replace("#", "") + if not tag in resolved_tags: + resolved_tags.append(tag) + + tags = resolved_tags + text_without_tags = text diff --git a/addons/dialogue_manager/compiler/resolved_tag_data.gd.uid b/addons/dialogue_manager/compiler/resolved_tag_data.gd.uid new file mode 100644 index 00000000..98c6f51d --- /dev/null +++ b/addons/dialogue_manager/compiler/resolved_tag_data.gd.uid @@ -0,0 +1 @@ +uid://cqai3ikuilqfq diff --git a/addons/dialogue_manager/compiler/tree_line.gd b/addons/dialogue_manager/compiler/tree_line.gd new file mode 100644 index 00000000..f172a8a4 --- /dev/null +++ b/addons/dialogue_manager/compiler/tree_line.gd @@ -0,0 +1,44 @@ +## An intermediate representation of a dialogue line before it gets compiled. +class_name DMTreeLine extends RefCounted + + +## The line number where this dialogue was found (after imported files have had their content imported). +var line_number: int = 0 +## The parent [DMTreeLine] of this line. +## This is stored as a Weak Reference so that this RefCounted can elegantly free itself. +## Without it being a Weak Reference, this can easily cause a cyclical reference that keeps this resource alive. +var parent: WeakRef +## The ID of this line. +var id: String +## The type of this line (as a [String] defined in [DMConstants]. +var type: String = "" +## Is this line part of a randomised group? +var is_random: bool = false +## The indent count for this line. +var indent: int = 0 +## The text of this line. +var text: String = "" +## The child [DMTreeLine]s of this line. +var children: Array[DMTreeLine] = [] +## Any doc comments attached to this line. +var notes: String = "" + + +func _init(initial_id: String) -> void: + id = initial_id + + +func _to_string() -> String: + var tabs = [] + tabs.resize(indent) + tabs.fill("\t") + tabs = "".join(tabs) + + return tabs.join([tabs + "{\n", + "\tid: %s\n" % [id], + "\ttype: %s\n" % [type], + "\tis_random: %s\n" % ["true" if is_random else "false"], + "\ttext: %s\n" % [text], + "\tnotes: %s\n" % [notes], + "\tchildren: []\n" if children.size() == 0 else "\tchildren: [\n" + ",\n".join(children.map(func(child): return str(child))) + "]\n", + "}"]) diff --git a/addons/dialogue_manager/compiler/tree_line.gd.uid b/addons/dialogue_manager/compiler/tree_line.gd.uid new file mode 100644 index 00000000..fe1db3a6 --- /dev/null +++ b/addons/dialogue_manager/compiler/tree_line.gd.uid @@ -0,0 +1 @@ +uid://dsu4i84dpif14 diff --git a/addons/dialogue_manager/components/code_edit.gd b/addons/dialogue_manager/components/code_edit.gd index e94bd469..e180af44 100644 --- a/addons/dialogue_manager/components/code_edit.gd +++ b/addons/dialogue_manager/components/code_edit.gd @@ -1,5 +1,5 @@ @tool -extends CodeEdit +class_name DMCodeEdit extends CodeEdit signal active_title_change(title: String) @@ -7,10 +7,6 @@ signal error_clicked(line_number: int) signal external_file_requested(path: String, title: String) -const DialogueManagerParser = preload("./parser.gd") -const DialogueSyntaxHighlighter = preload("./code_edit_syntax_highlighter.gd") - - # A link back to the owner `MainView` var main_view @@ -19,7 +15,7 @@ var theme_overrides: Dictionary: set(value): theme_overrides = value - syntax_highlighter = DialogueSyntaxHighlighter.new() + syntax_highlighter = DMSyntaxHighlighter.new() # General UI add_theme_color_override("font_color", theme_overrides.text_color) @@ -67,7 +63,7 @@ func _ready() -> void: if not has_comment_delimiter("#"): add_comment_delimiter("#", "", true) - syntax_highlighter = DialogueSyntaxHighlighter.new() + syntax_highlighter = DMSyntaxHighlighter.new() func _gui_input(event: InputEvent) -> void: @@ -162,17 +158,18 @@ func _request_code_completion(force: bool) -> void: add_code_completion_option(CodeEdit.KIND_CLASS, "END!", "END!".substr(prompt.length()), theme_overrides.text_color, get_theme_icon("Stop", "EditorIcons")) # Get all titles, including those in imports - var parser: DialogueManagerParser = DialogueManagerParser.new() - parser.prepare(text, main_view.current_file_path, false) - for title in parser.titles: - if "/" in title: + for title: String in DMCompiler.get_titles_in_text(text, main_view.current_file_path): + # Ignore any imported titles that aren't resolved to human readable. + if title.to_int() > 0: + continue + + elif "/" in title: var bits = title.split("/") if matches_prompt(prompt, bits[0]) or matches_prompt(prompt, bits[1]): add_code_completion_option(CodeEdit.KIND_CLASS, title, title.substr(prompt.length()), theme_overrides.text_color, get_theme_icon("CombineLines", "EditorIcons")) elif matches_prompt(prompt, title): add_code_completion_option(CodeEdit.KIND_CLASS, title, title.substr(prompt.length()), theme_overrides.text_color, get_theme_icon("ArrowRight", "EditorIcons")) update_code_completion_options(true) - parser.free() return var name_so_far: String = WEIGHTED_RANDOM_PREFIX.sub(current_line.strip_edges(), "") @@ -232,6 +229,7 @@ func get_titles() -> PackedStringArray: for line in lines: if line.strip_edges().begins_with("~ "): titles.append(line.strip_edges().substr(2)) + return titles @@ -270,6 +268,11 @@ func get_character_names(beginning_with: String) -> PackedStringArray: # Mark a line as an error or not func mark_line_as_error(line_number: int, is_error: bool) -> void: + # Lines display counting from 1 but are actually indexed from 0 + line_number -= 1 + + if line_number < 0: return + if is_error: set_line_background_color(line_number, theme_overrides.error_line_color) set_line_gutter_icon(line_number, 0, get_theme_icon("StatusError", "EditorIcons")) diff --git a/addons/dialogue_manager/components/code_edit.gd.uid b/addons/dialogue_manager/components/code_edit.gd.uid new file mode 100644 index 00000000..ab2b9e57 --- /dev/null +++ b/addons/dialogue_manager/components/code_edit.gd.uid @@ -0,0 +1 @@ +uid://djeybvlb332mp diff --git a/addons/dialogue_manager/components/code_edit.tscn b/addons/dialogue_manager/components/code_edit.tscn index a974ea34..0c25707b 100644 --- a/addons/dialogue_manager/components/code_edit.tscn +++ b/addons/dialogue_manager/components/code_edit.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=4 format=3 uid="uid://civ6shmka5e8u"] -[ext_resource type="Script" path="res://addons/dialogue_manager/components/code_edit_syntax_highlighter.gd" id="1_58cfo"] -[ext_resource type="Script" path="res://addons/dialogue_manager/components/code_edit.gd" id="1_g324i"] +[ext_resource type="Script" uid="uid://klpiq4tk3t7a" path="res://addons/dialogue_manager/components/code_edit_syntax_highlighter.gd" id="1_58cfo"] +[ext_resource type="Script" uid="uid://djeybvlb332mp" path="res://addons/dialogue_manager/components/code_edit.gd" id="1_g324i"] [sub_resource type="SyntaxHighlighter" id="SyntaxHighlighter_cobxx"] script = ExtResource("1_58cfo") diff --git a/addons/dialogue_manager/components/code_edit_syntax_highlighter.gd b/addons/dialogue_manager/components/code_edit_syntax_highlighter.gd index 7e4a98f3..6f73794c 100644 --- a/addons/dialogue_manager/components/code_edit_syntax_highlighter.gd +++ b/addons/dialogue_manager/components/code_edit_syntax_highlighter.gd @@ -1,55 +1,18 @@ @tool -extends SyntaxHighlighter +class_name DMSyntaxHighlighter extends SyntaxHighlighter -const DialogueManagerParser = preload("./parser.gd") - - -enum ExpressionType {DO, SET, IF} - - -var dialogue_manager_parser: DialogueManagerParser = DialogueManagerParser.new() - -var regex_titles: RegEx = RegEx.create_from_string("^\\s*(?~\\s+[^\\!\\@\\#\\$\\%\\^\\&\\*\\(\\)\\-\\=\\+\\{\\}\\[\\]\\;\\:\\\"\\'\\,\\.\\<\\>\\?\\/\\s]+)") -var regex_comments: RegEx = RegEx.create_from_string("(?:(?>\"(?:\\\\\"|[^\"\\n])*\")[^\"\\n]*?\\s*(?<comment>#[^\\n]*)$|^[^\"#\\n]*?\\s*(?<comment2>#[^\\n]*))") -var regex_mutation: RegEx = RegEx.create_from_string("^\\s*(do|do!|set) (?<mutation>.*)") -var regex_condition: RegEx = RegEx.create_from_string("^\\s*(if|elif|while|else if) (?<condition>.*)") -var regex_wcondition: RegEx = RegEx.create_from_string("\\[if (?<condition>((?:[^\\[\\]]*)|(?:\\[(?1)\\]))*?)\\]") -var regex_wendif: RegEx = RegEx.create_from_string("\\[(\\/if|else)\\]") -var regex_rgroup: RegEx = RegEx.create_from_string("\\[\\[(?<options>.*?)\\]\\]") -var regex_endconditions: RegEx = RegEx.create_from_string("^\\s*(endif|else):?\\s*$") -var regex_tags: RegEx = RegEx.create_from_string("\\[(?<tag>(?!(?:ID:.*)|if)[a-zA-Z_][a-zA-Z0-9_]*!?)(?:[= ](?<val>[^\\[\\]]+))?\\](?:(?<text>(?!\\[\\/\\k<tag>\\]).*?)?(?<end>\\[\\/\\k<tag>\\]))?") -var regex_dialogue: RegEx = RegEx.create_from_string("^\\s*(?:(?<random>\\%[\\d.]* )|(?<response>- ))?(?:(?<character>[^#:]*): )?(?<dialogue>.*)$") -var regex_goto: RegEx = RegEx.create_from_string("=><? (?:(?<file>[^\\/]+)\\/)?(?<title>[^\\/]*)") -var regex_string: RegEx = RegEx.create_from_string("^&?(?<delimiter>[\"'])(?<content>(?:\\\\{2})*|(?:.*?[^\\\\](?:\\\\{2})*))\\1$") -var regex_escape: RegEx = RegEx.create_from_string("\\\\.") -var regex_number: RegEx = RegEx.create_from_string("^-?(?:(?:0x(?:[0-9A-Fa-f]{2})+)|(?:0b[01]+)|(?:\\d+(?:(?:[\\.]\\d*)?(?:e\\d+)?)|(?:_\\d+)+)?)$") -var regex_array: RegEx = RegEx.create_from_string("\\[((?>[^\\[\\]]+|(?R))*)\\]") -var regex_dict: RegEx = RegEx.create_from_string("^\\{((?>[^\\{\\}]+|(?R))*)\\}$") -var regex_kvdict: RegEx = RegEx.create_from_string("^\\s*(?<left>.*?)\\s*(?<colon>:|=)\\s*(?<right>[^\\/]+)$") -var regex_commas: RegEx = RegEx.create_from_string("([^,]+)(?:\\s*,\\s*)?") -var regex_assignment: RegEx = RegEx.create_from_string("^\\s*(?<var>[a-zA-Z_][a-zA-Z_0-9]*)(?:(?<attr>(?:\\.[a-zA-Z_][a-zA-Z_0-9]*)+)|(?:\\[(?<key>[^\\]]+)\\]))?\\s*(?<op>(?:\\/|\\*|-|\\+)?=)\\s*(?<val>.*)$") -var regex_varname: RegEx = RegEx.create_from_string("^\\s*(?!true|false|and|or|&&|\\|\\|not|in|null)(?<var>[a-zA-Z_][a-zA-Z_0-9]*)(?:(?<attr>(?:\\.[a-zA-Z_][a-zA-Z_0-9]*)+)|(?:\\[(?<key>[^\\]]+)\\]))?\\s*$") -var regex_keyword: RegEx = RegEx.create_from_string("^\\s*(true|false|null)\\s*$") -var regex_function: RegEx = RegEx.create_from_string("^\\s*([a-zA-Z_][a-zA-Z_0-9]*\\s*)\\(") -var regex_comparison: RegEx = RegEx.create_from_string("^(?<left>.*?)\\s*(?<op>==|>=|<=|<|>|!=)\\s*(?<right>.*)$") -var regex_blogical: RegEx = RegEx.create_from_string("^(?<left>.*?)\\s+(?<op>and|or|in|&&|\\|\\|)\\s+(?<right>.*)$") -var regex_ulogical: RegEx = RegEx.create_from_string("^\\s*(?<op>not)\\s+(?<right>.*)$") -var regex_paren: RegEx = RegEx.create_from_string("\\((?<paren>((?:[^\\(\\)]*)|(?:\\((?1)\\)))*?)\\)") +var regex: DMCompilerRegEx = DMCompilerRegEx.new() +var compilation: DMCompilation = DMCompilation.new() +var expression_parser = DMExpressionParser.new() var cache: Dictionary = {} -func _notification(what: int) -> void: - if what == NOTIFICATION_PREDELETE: - dialogue_manager_parser.free() - - func _clear_highlighting_cache() -> void: - cache = {} + cache.clear() -## Returns the syntax coloring for a dialogue file line func _get_line_syntax_highlighting(line: int) -> Dictionary: var colors: Dictionary = {} var text_edit: TextEdit = get_text_edit() @@ -63,323 +26,183 @@ func _get_line_syntax_highlighting(line: int) -> Dictionary: if text in cache: return cache[text] - # Comments have to be removed to make the remaining processing easier. - # Count both end-of-line and single-line comments - # Comments are not allowed within dialogue lines or response lines, so we ask the parser what it thinks the current line is - if not (dialogue_manager_parser.is_dialogue_line(text) or dialogue_manager_parser.is_response_line(text)) or dialogue_manager_parser.is_line_empty(text) or dialogue_manager_parser.is_import_line(text): - var comment_matches: Array[RegExMatch] = regex_comments.search_all(text) - for comment_match in comment_matches: - for i in ["comment", "comment2"]: - if i in comment_match.names: - colors[comment_match.get_start(i)] = {"color": text_edit.theme_overrides.comments_color} - text = text.substr(0, comment_match.get_start(i)) + var theme: Dictionary = text_edit.theme_overrides - # Dialogues - var dialogue_matches: Array[RegExMatch] = regex_dialogue.search_all(text) - for dialogue_match in dialogue_matches: - if "random" in dialogue_match.names: - colors[dialogue_match.get_start("random")] = {"color": text_edit.theme_overrides.symbols_color} - colors[dialogue_match.get_end("random")] = {"color": text_edit.theme_overrides.text_color} - if "response" in dialogue_match.names: - colors[dialogue_match.get_start("response")] = {"color": text_edit.theme_overrides.symbols_color} - colors[dialogue_match.get_end("response")] = {"color": text_edit.theme_overrides.text_color} - if "character" in dialogue_match.names: - colors[dialogue_match.get_start("character")] = {"color": text_edit.theme_overrides.members_color} - colors[dialogue_match.get_end("character")] = {"color": text_edit.theme_overrides.text_color} - colors.merge(_get_dialogue_syntax_highlighting(dialogue_match.get_start("dialogue"), dialogue_match.get_string("dialogue")), true) + var index: int = 0 - # Title lines - if dialogue_manager_parser.is_title_line(text): - var title_matches: Array[RegExMatch] = regex_titles.search_all(text) - for title_match in title_matches: - colors[title_match.get_start("title")] = {"color": text_edit.theme_overrides.titles_color} + match DMCompiler.get_line_type(text): + DMConstants.TYPE_COMMENT: + colors[index] = { color = theme.comments_color } - # Import lines - var import_matches: Array[RegExMatch] = dialogue_manager_parser.IMPORT_REGEX.search_all(text) - for import_match in import_matches: - colors[import_match.get_start(0)] = {"color": text_edit.theme_overrides.conditions_color} - colors[import_match.get_start("path") - 1] = {"color": text_edit.theme_overrides.strings_color} - colors[import_match.get_end("path") + 1] = {"color": text_edit.theme_overrides.conditions_color} - colors[import_match.get_start("prefix")] = {"color": text_edit.theme_overrides.members_color} - colors[import_match.get_end("prefix")] = {"color": text_edit.theme_overrides.conditions_color} + DMConstants.TYPE_TITLE: + colors[index] = { color = theme.titles_color } - # Using clauses - var using_matches: Array[RegExMatch] = dialogue_manager_parser.USING_REGEX.search_all(text) - for using_match in using_matches: - colors[using_match.get_start(0)] = {"color": text_edit.theme_overrides.conditions_color} - colors[using_match.get_start("state") - 1] = {"color": text_edit.theme_overrides.text_color} + DMConstants.TYPE_CONDITION, DMConstants.TYPE_WHILE, DMConstants.TYPE_MATCH, DMConstants.TYPE_WHEN: + colors[0] = { color = theme.conditions_color } + index = text.find(" ") + if index > -1: + var expression: Array = expression_parser.tokenise(text.substr(index), DMConstants.TYPE_CONDITION, 0) + if expression.size() == 0 or expression[0].type == DMConstants.TYPE_ERROR: + colors[index] = { color = theme.critical_color } + else: + _highlight_expression(expression, colors, index) - # Condition keywords and expressions - var condition_matches: Array[RegExMatch] = regex_condition.search_all(text) - for condition_match in condition_matches: - colors[condition_match.get_start(0)] = {"color": text_edit.theme_overrides.conditions_color} - colors[condition_match.get_end(1)] = {"color": text_edit.theme_overrides.text_color} - colors.merge(_get_expression_syntax_highlighting(condition_match.get_start("condition"), ExpressionType.IF, condition_match.get_string("condition")), true) - # endif/else - var endcondition_matches: Array[RegExMatch] = regex_endconditions.search_all(text) - for endcondition_match in endcondition_matches: - colors[endcondition_match.get_start(1)] = {"color": text_edit.theme_overrides.conditions_color} - colors[endcondition_match.get_end(1)] = {"color": text_edit.theme_overrides.symbols_color} + DMConstants.TYPE_MUTATION: + colors[0] = { color = theme.mutations_color } + index = text.find(" ") + var expression: Array = expression_parser.tokenise(text.substr(index), DMConstants.TYPE_MUTATION, 0) + if expression.size() == 0 or expression[0].type == DMConstants.TYPE_ERROR: + colors[index] = { color = theme.critical_color } + else: + _highlight_expression(expression, colors, index) - # Mutations - var mutation_matches: Array[RegExMatch] = regex_mutation.search_all(text) - for mutation_match in mutation_matches: - colors[mutation_match.get_start(0)] = {"color": text_edit.theme_overrides.mutations_color} - colors.merge(_get_expression_syntax_highlighting(mutation_match.get_start("mutation"), ExpressionType.DO if mutation_match.strings[1].begins_with("do") else ExpressionType.SET, mutation_match.get_string("mutation")), true) + DMConstants.TYPE_GOTO: + if text.strip_edges().begins_with("%"): + colors[index] = { color = theme.symbols_color } + index = text.find(" ") + _highlight_goto(text, colors, index) + + DMConstants.TYPE_RANDOM: + colors[index] = { color = theme.symbols_color } + + DMConstants.TYPE_DIALOGUE, DMConstants.TYPE_RESPONSE: + if text.strip_edges().begins_with("%"): + colors[index] = { color = theme.symbols_color } + index = text.find(" ", text.find("%")) + colors[index] = { color = theme.text_color.lerp(theme.symbols_color, 0.5) } + + var dialogue_text: String = text.substr(index, text.find("=>")) + + # Highlight character name + var split_index: int = dialogue_text.replace("\\:", "??").find(":") + colors[index + split_index + 1] = { color = theme.text_color } + + # Interpolation + var replacements: Array[RegExMatch] = regex.REPLACEMENTS_REGEX.search_all(dialogue_text) + for replacement: RegExMatch in replacements: + var expression_text: String = replacement.get_string().substr(0, replacement.get_string().length() - 2).substr(2) + var expression: Array = expression_parser.tokenise(expression_text, DMConstants.TYPE_MUTATION, replacement.get_start()) + var expression_index: int = index + replacement.get_start() + colors[expression_index] = { color = theme.symbols_color } + if expression.size() == 0 or expression[0].type == DMConstants.TYPE_ERROR: + colors[expression_index] = { color = theme.critical_color } + else: + _highlight_expression(expression, colors, index + 2) + colors[expression_index + expression_text.length() + 2] = { color = theme.symbols_color } + colors[expression_index + expression_text.length() + 4] = { color = theme.text_color } + # Tags (and inline mutations) + var resolved_line_data: DMResolvedLineData = DMResolvedLineData.new("") + var bbcodes: Array[Dictionary] = resolved_line_data.find_bbcode_positions_in_string(dialogue_text, true, true) + for bbcode: Dictionary in bbcodes: + var tag: String = bbcode.code + var code: String = bbcode.raw_args + if code.begins_with("["): + colors[index + bbcode.start] = { color = theme.symbols_color } + colors[index + bbcode.start + 2] = { color = theme.text_color } + var pipe_cursor: int = code.find("|") + while pipe_cursor > -1: + colors[index + bbcode.start + pipe_cursor + 1] = { color = theme.symbols_color } + colors[index + bbcode.start + pipe_cursor + 2] = { color = theme.text_color } + pipe_cursor = code.find("|", pipe_cursor + 1) + colors[index + bbcode.end - 1] = { color = theme.symbols_color } + colors[index + bbcode.end + 1] = { color = theme.text_color } + else: + colors[index + bbcode.start] = { color = theme.symbols_color } + if tag.begins_with("do") or tag.begins_with("set") or tag.begins_with("if"): + if tag.begins_with("if"): + colors[index + bbcode.start + 1] = { color = theme.conditions_color } + else: + colors[index + bbcode.start + 1] = { color = theme.mutations_color } + var expression: Array = expression_parser.tokenise(code, DMConstants.TYPE_MUTATION, bbcode.start + bbcode.code.length()) + if expression.size() == 0 or expression[0].type == DMConstants.TYPE_ERROR: + colors[index + bbcode.start + tag.length() + 1] = { color = theme.critical_color } + else: + _highlight_expression(expression, colors, index + 2) + # else and closing if have no expression + elif tag.begins_with("else") or tag.begins_with("/if"): + colors[index + bbcode.start + 1] = { color = theme.conditions_color } + colors[index + bbcode.end] = { color = theme.symbols_color } + colors[index + bbcode.end + 1] = { color = theme.text_color } + # Jumps + if "=> " in text or "=>< " in text: + _highlight_goto(text, colors, index) # Order the dictionary keys to prevent CodeEdit from having issues - var new_colors: Dictionary = {} + var ordered_colors: Dictionary = {} var ordered_keys: Array = colors.keys() ordered_keys.sort() - for index in ordered_keys: - new_colors[index] = colors[index] + for key_index: int in ordered_keys: + ordered_colors[key_index] = colors[key_index] - cache[text] = new_colors - return new_colors + cache[text] = ordered_colors + return ordered_colors -## Return the syntax highlighting for a dialogue line -func _get_dialogue_syntax_highlighting(start_index: int, text: String) -> Dictionary: - var text_edit: TextEdit = get_text_edit() - var colors: Dictionary = {} +func _highlight_expression(tokens: Array, colors: Dictionary, index: int) -> int: + var theme: Dictionary = get_text_edit().theme_overrides + var last_index: int = index + for token: Dictionary in tokens: + last_index = token.i + match token.type: + DMConstants.TOKEN_CONDITION, DMConstants.TOKEN_AND_OR: + colors[index + token.i] = { color = theme.conditions_color } - # #tag style tags - var hashtag_matches: Array[RegExMatch] = dialogue_manager_parser.TAGS_REGEX.search_all(text) - for hashtag_match in hashtag_matches: - colors[start_index + hashtag_match.get_start(0)] = { "color": text_edit.theme_overrides.comments_color } - colors[start_index + hashtag_match.get_end(0)] = { "color": text_edit.theme_overrides.text_color } + DMConstants.TOKEN_VARIABLE: + if token.value in ["true", "false"]: + colors[index + token.i] = { color = theme.conditions_color } + else: + colors[index + token.i] = { color = theme.members_color } - # bbcode-like global tags - var tag_matches: Array[RegExMatch] = regex_tags.search_all(text) - for tag_match in tag_matches: - colors[start_index + tag_match.get_start(0)] = {"color": text_edit.theme_overrides.symbols_color} - if "val" in tag_match.names: - colors.merge(_get_literal_syntax_highlighting(start_index + tag_match.get_start("val"), tag_match.get_string("val")), true) - colors[start_index + tag_match.get_end("val")] = {"color": text_edit.theme_overrides.symbols_color} - # Show the text color straight in the editor for better ease-of-use - if tag_match.get_string("tag") == "color": - colors[start_index + tag_match.get_start("val")] = {"color": Color.from_string(tag_match.get_string("val"), text_edit.theme_overrides.text_color)} - if "text" in tag_match.names: - colors[start_index + tag_match.get_start("text")] = {"color": text_edit.theme_overrides.text_color} - # Text can still contain tags if several effects are applied ([center][b]Something[/b][/center], so recursing - colors.merge(_get_dialogue_syntax_highlighting(start_index + tag_match.get_start("text"), tag_match.get_string("text")), true) - colors[start_index + tag_match.get_end("text")] = {"color": text_edit.theme_overrides.symbols_color} - if "end" in tag_match.names: - colors[start_index + tag_match.get_start("end")] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + tag_match.get_end("end")] = {"color": text_edit.theme_overrides.text_color} - colors[start_index + tag_match.get_end(0)] = {"color": text_edit.theme_overrides.text_color} + DMConstants.TOKEN_OPERATOR, DMConstants.TOKEN_COLON, DMConstants.TOKEN_COMMA, DMConstants.TOKEN_NUMBER, DMConstants.TOKEN_ASSIGNMENT: + colors[index + token.i] = { color = theme.symbols_color } - # ID tag - var translation_matches: Array[RegExMatch] = dialogue_manager_parser.TRANSLATION_REGEX.search_all(text) - for translation_match in translation_matches: - colors[start_index + translation_match.get_start(0)] = {"color": text_edit.theme_overrides.comments_color} - colors[start_index + translation_match.get_end(0)] = {"color": text_edit.theme_overrides.text_color} + DMConstants.TOKEN_STRING: + colors[index + token.i] = { color = theme.strings_color } - # Replacements - var replacement_matches: Array[RegExMatch] = dialogue_manager_parser.REPLACEMENTS_REGEX.search_all(text) - for replacement_match in replacement_matches: - colors[start_index + replacement_match.get_start(0)] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + replacement_match.get_start(1)] = {"color": text_edit.theme_overrides.text_color} - colors.merge(_get_literal_syntax_highlighting(start_index + replacement_match.get_start(1), replacement_match.strings[1]), true) - colors[start_index + replacement_match.get_end(1)] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + replacement_match.get_end(0)] = {"color": text_edit.theme_overrides.text_color} + DMConstants.TOKEN_FUNCTION: + colors[index + token.i] = { color = theme.mutations_color } + colors[index + token.i + token.function.length()] = { color = theme.symbols_color } + for parameter: Array in token.value: + last_index = _highlight_expression(parameter, colors, index) + DMConstants.TOKEN_PARENS_CLOSE: + colors[index + token.i] = { color = theme.symbols_color } - # Jump at the end of a response - var goto_matches: Array[RegExMatch] = regex_goto.search_all(text) - for goto_match in goto_matches: - colors[start_index + goto_match.get_start(0)] = {"color": text_edit.theme_overrides.jumps_color} - if "file" in goto_match.names: - colors[start_index + goto_match.get_start("file")] = {"color": text_edit.theme_overrides.jumps_color} - colors[start_index + goto_match.get_end("file")] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + goto_match.get_start("title")] = {"color": text_edit.theme_overrides.jumps_color} - colors[start_index + goto_match.get_end("title")] = {"color": text_edit.theme_overrides.jumps_color} - colors[start_index + goto_match.get_end(0)] = {"color": text_edit.theme_overrides.text_color} + DMConstants.TOKEN_DICTIONARY_REFERENCE: + colors[index + token.i] = { color = theme.members_color } + colors[index + token.i + token.variable.length()] = { color = theme.symbols_color } + last_index = _highlight_expression(token.value, colors, index) + DMConstants.TOKEN_ARRAY: + colors[index + token.i] = { color = theme.symbols_color } + for item: Array in token.value: + last_index = _highlight_expression(item, colors, index) + DMConstants.TOKEN_BRACKET_CLOSE: + colors[index + token.i] = { color = theme.symbols_color } - # Wrapped condition - var wcondition_matches: Array[RegExMatch] = regex_wcondition.search_all(text) - for wcondition_match in wcondition_matches: - colors[start_index + wcondition_match.get_start(0)] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + wcondition_match.get_start(0) + 1] = {"color": text_edit.theme_overrides.conditions_color} - colors[start_index + wcondition_match.get_start(0) + 3] = {"color": text_edit.theme_overrides.text_color} - colors.merge(_get_literal_syntax_highlighting(start_index + wcondition_match.get_start("condition"), wcondition_match.get_string("condition")), true) - colors[start_index + wcondition_match.get_end("condition")] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + wcondition_match.get_end(0)] = {"color": text_edit.theme_overrides.text_color} - # [/if] tag for color matching with the opening tag - var wendif_matches: Array[RegExMatch] = regex_wendif.search_all(text) - for wendif_match in wendif_matches: - colors[start_index + wendif_match.get_start(0)] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + wendif_match.get_start(1)] = {"color": text_edit.theme_overrides.conditions_color} - colors[start_index + wendif_match.get_end(1)] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + wendif_match.get_end(0)] = {"color": text_edit.theme_overrides.text_color} + DMConstants.TOKEN_DICTIONARY: + colors[index + token.i] = { color = theme.symbols_color } + last_index = _highlight_expression(token.value.keys() + token.value.values(), colors, index) + DMConstants.TOKEN_BRACE_CLOSE: + colors[index + token.i] = { color = theme.symbols_color } + last_index += 1 - # Random groups - var rgroup_matches: Array[RegExMatch] = regex_rgroup.search_all(text) - for rgroup_match in rgroup_matches: - colors[start_index + rgroup_match.get_start(0)] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + rgroup_match.get_start("options")] = {"color": text_edit.theme_overrides.text_color} - var separator_matches: Array[RegExMatch] = RegEx.create_from_string("\\|").search_all(rgroup_match.get_string("options")) - for separator_match in separator_matches: - colors[start_index + rgroup_match.get_start("options") + separator_match.get_start(0)] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + rgroup_match.get_start("options") + separator_match.get_end(0)] = {"color": text_edit.theme_overrides.text_color} - colors[start_index + rgroup_match.get_end("options")] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + rgroup_match.get_end(0)] = {"color": text_edit.theme_overrides.text_color} + DMConstants.TOKEN_GROUP: + last_index = _highlight_expression(token.value, colors, index) - return colors + return last_index -## Returns the syntax highlighting for an expression (mutation set/do, or condition) -func _get_expression_syntax_highlighting(start_index: int, type: ExpressionType, text: String) -> Dictionary: - var text_edit: TextEdit = get_text_edit() - var colors: Dictionary = {} +func _highlight_goto(text: String, colors: Dictionary, index: int) -> int: + var theme: Dictionary = get_text_edit().theme_overrides + var goto_data: DMResolvedGotoData = DMResolvedGotoData.new(text, {}) + colors[goto_data.index] = { color = theme.jumps_color } + if "{{" in text: + index = text.find("{{", goto_data.index) + var last_index: int = 0 + if goto_data.error: + colors[index + 2] = { color = theme.critical_color } + else: + last_index = _highlight_expression(goto_data.expression, colors, index) + index = text.find("}}", index + last_index) + colors[index] = { color = theme.jumps_color } - if type == ExpressionType.SET: - var assignment_matches: Array[RegExMatch] = regex_assignment.search_all(text) - for assignment_match in assignment_matches: - colors[start_index + assignment_match.get_start("var")] = {"color": text_edit.theme_overrides.text_color} - if "attr" in assignment_match.names: - colors[start_index + assignment_match.get_start("attr")] = {"color": text_edit.theme_overrides.members_color} - colors[start_index + assignment_match.get_end("attr")] = {"color": text_edit.theme_overrides.text_color} - if "key" in assignment_match.names: - # Braces are outside of the key, so coloring them symbols_color - colors[start_index + assignment_match.get_start("key") - 1] = {"color": text_edit.theme_overrides.symbols_color} - colors.merge(_get_literal_syntax_highlighting(start_index + assignment_match.get_start("key"), assignment_match.get_string("key")), true) - colors[start_index + assignment_match.get_end("key")] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + assignment_match.get_end("key") + 1] = {"color": text_edit.theme_overrides.text_color} - - colors[start_index + assignment_match.get_start("op")] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + assignment_match.get_end("op")] = {"color": text_edit.theme_overrides.text_color} - colors.merge(_get_literal_syntax_highlighting(start_index + assignment_match.get_start("val"), assignment_match.get_string("val")), true) - else: - colors.merge(_get_literal_syntax_highlighting(start_index, text), true) - - return colors - - -## Return the syntax highlighting for a literal -## For this purpose, "literal" refers to a regular code line that could be used to get a value out of: -## - function calls -## - real literals (bool, string, int, float, etc.) -## - logical operators (>, <, >=, or, and, not, etc.) -func _get_literal_syntax_highlighting(start_index: int, text: String) -> Dictionary: - var text_edit: TextEdit = get_text_edit() - var colors: Dictionary = {} - - # Remove spaces at start/end of the literal - var text_length: int = text.length() - text = text.lstrip(" ") - start_index += text_length - text.length() - text = text.rstrip(" ") - - # Parenthesis expression. - var paren_matches: Array[RegExMatch] = regex_paren.search_all(text) - for paren_match in paren_matches: - colors[start_index + paren_match.get_start(0)] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + paren_match.get_start(0) + 1] = {"color": text_edit.theme_overrides.text_color} - colors.merge(_get_literal_syntax_highlighting(start_index + paren_match.get_start("paren"), paren_match.get_string("paren")), true) - colors[start_index + paren_match.get_end(0) - 1] = {"color": text_edit.theme_overrides.symbols_color} - - # Strings - var string_matches: Array[RegExMatch] = regex_string.search_all(text) - for string_match in string_matches: - colors[start_index + string_match.get_start(0)] = {"color": text_edit.theme_overrides.strings_color} - if "content" in string_match.names: - var escape_matches: Array[RegExMatch] = regex_escape.search_all(string_match.get_string("content")) - for escape_match in escape_matches: - colors[start_index + string_match.get_start("content") + escape_match.get_start(0)] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + string_match.get_start("content") + escape_match.get_end(0)] = {"color": text_edit.theme_overrides.strings_color} - - # Numbers - var number_matches: Array[RegExMatch] = regex_number.search_all(text) - for number_match in number_matches: - colors[start_index + number_match.get_start(0)] = {"color": text_edit.theme_overrides.numbers_color} - - # Arrays - var array_matches: Array[RegExMatch] = regex_array.search_all(text) - for array_match in array_matches: - colors[start_index + array_match.get_start(0)] = {"color": text_edit.theme_overrides.symbols_color} - colors.merge(_get_list_syntax_highlighting(start_index + array_match.get_start(1), array_match.strings[1]), true) - colors[start_index + array_match.get_end(1)] = {"color": text_edit.theme_overrides.symbols_color} - - # Dictionaries - var dict_matches: Array[RegExMatch] = regex_dict.search_all(text) - for dict_match in dict_matches: - colors[start_index + dict_match.get_start(0)] = {"color": text_edit.theme_overrides.symbols_color} - colors.merge(_get_list_syntax_highlighting(start_index + dict_match.get_start(1), dict_match.strings[1]), true) - colors[start_index + dict_match.get_end(1)] = {"color": text_edit.theme_overrides.symbols_color} - - # Dictionary key: value pairs - var kvdict_matches: Array[RegExMatch] = regex_kvdict.search_all(text) - for kvdict_match in kvdict_matches: - colors.merge(_get_literal_syntax_highlighting(start_index + kvdict_match.get_start("left"), kvdict_match.get_string("left")), true) - colors[start_index + kvdict_match.get_start("colon")] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + kvdict_match.get_end("colon")] = {"color": text_edit.theme_overrides.text_color} - colors.merge(_get_literal_syntax_highlighting(start_index + kvdict_match.get_start("right"), kvdict_match.get_string("right")), true) - - # Booleans - var bool_matches: Array[RegExMatch] = regex_keyword.search_all(text) - for bool_match in bool_matches: - colors[start_index + bool_match.get_start(0)] = {"color": text_edit.theme_overrides.conditions_color} - - # Functions - var function_matches: Array[RegExMatch] = regex_function.search_all(text) - for function_match in function_matches: - var last_brace_index: int = text.rfind(")") - colors[start_index + function_match.get_start(1)] = {"color": text_edit.theme_overrides.mutations_color} - colors[start_index + function_match.get_end(1)] = {"color": text_edit.theme_overrides.symbols_color} - colors.merge(_get_list_syntax_highlighting(start_index + function_match.get_end(0), text.substr(function_match.get_end(0), last_brace_index - function_match.get_end(0))), true) - colors[start_index + last_brace_index] = {"color": text_edit.theme_overrides.symbols_color} - - # Variables - var varname_matches: Array[RegExMatch] = regex_varname.search_all(text) - for varname_match in varname_matches: - colors[start_index + varname_match.get_start("var")] = {"color": text_edit.theme_overrides.text_color} - if "attr" in varname_match.names: - colors[start_index + varname_match.get_start("attr")] = {"color": text_edit.theme_overrides.members_color} - colors[start_index + varname_match.get_end("attr")] = {"color": text_edit.theme_overrides.text_color} - if "key" in varname_match.names: - # Braces are outside of the key, so coloring them symbols_color - colors[start_index + varname_match.get_start("key") - 1] = {"color": text_edit.theme_overrides.symbols_color} - colors.merge(_get_literal_syntax_highlighting(start_index + varname_match.get_start("key"), varname_match.get_string("key")), true) - colors[start_index + varname_match.get_end("key")] = {"color": text_edit.theme_overrides.symbols_color} - - # Comparison operators - var comparison_matches: Array[RegExMatch] = regex_comparison.search_all(text) - for comparison_match in comparison_matches: - colors.merge(_get_literal_syntax_highlighting(start_index + comparison_match.get_start("left"), comparison_match.get_string("left")), true) - colors[start_index + comparison_match.get_start("op")] = {"color": text_edit.theme_overrides.symbols_color} - colors[start_index + comparison_match.get_end("op")] = {"color": text_edit.theme_overrides.text_color} - var right = comparison_match.get_string("right") - if right.ends_with(":"): - right = right.substr(0, right.length() - 1) - colors.merge(_get_literal_syntax_highlighting(start_index + comparison_match.get_start("right"), right), true) - colors[start_index + comparison_match.get_start("right") + right.length()] = { "color": text_edit.theme_overrides.symbols_color } - - # Logical binary operators - var blogical_matches: Array[RegExMatch] = regex_blogical.search_all(text) - for blogical_match in blogical_matches: - colors.merge(_get_literal_syntax_highlighting(start_index + blogical_match.get_start("left"), blogical_match.get_string("left")), true) - colors[start_index + blogical_match.get_start("op")] = {"color": text_edit.theme_overrides.conditions_color} - colors[start_index + blogical_match.get_end("op")] = {"color": text_edit.theme_overrides.text_color} - colors.merge(_get_literal_syntax_highlighting(start_index + blogical_match.get_start("right"), blogical_match.get_string("right")), true) - - # Logical unary operators - var ulogical_matches: Array[RegExMatch] = regex_ulogical.search_all(text) - for ulogical_match in ulogical_matches: - colors[start_index + ulogical_match.get_start("op")] = {"color": text_edit.theme_overrides.conditions_color} - colors[start_index + ulogical_match.get_end("op")] = {"color": text_edit.theme_overrides.text_color} - colors.merge(_get_literal_syntax_highlighting(start_index + ulogical_match.get_start("right"), ulogical_match.get_string("right")), true) - - return colors - - -## Returns the syntax coloring for a list of literals separated by commas -func _get_list_syntax_highlighting(start_index: int, text: String) -> Dictionary: - var text_edit: TextEdit = get_text_edit() - var colors: Dictionary = {} - - # Comma-separated list of literals (for arrays and function arguments) - var element_matches: Array[RegExMatch] = regex_commas.search_all(text) - for element_match in element_matches: - colors.merge(_get_literal_syntax_highlighting(start_index + element_match.get_start(1), element_match.strings[1]), true) - - return colors + return index diff --git a/addons/dialogue_manager/components/code_edit_syntax_highlighter.gd.uid b/addons/dialogue_manager/components/code_edit_syntax_highlighter.gd.uid new file mode 100644 index 00000000..9bad8cc9 --- /dev/null +++ b/addons/dialogue_manager/components/code_edit_syntax_highlighter.gd.uid @@ -0,0 +1 @@ +uid://klpiq4tk3t7a diff --git a/addons/dialogue_manager/components/download_update_panel.gd b/addons/dialogue_manager/components/download_update_panel.gd index 617d3082..e67a93ff 100644 --- a/addons/dialogue_manager/components/download_update_panel.gd +++ b/addons/dialogue_manager/components/download_update_panel.gd @@ -34,7 +34,7 @@ func _ready() -> void: func _on_download_button_pressed() -> void: # Safeguard the actual dialogue manager repo from accidentally updating itself - if FileAccess.file_exists("res://examples/test_scenes/test_scene.gd"): + if FileAccess.file_exists("res://tests/test_basic_dialogue.gd"): prints("You can't update the addon from within itself.") failed.emit() return diff --git a/addons/dialogue_manager/components/download_update_panel.gd.uid b/addons/dialogue_manager/components/download_update_panel.gd.uid new file mode 100644 index 00000000..7910ab4a --- /dev/null +++ b/addons/dialogue_manager/components/download_update_panel.gd.uid @@ -0,0 +1 @@ +uid://kpwo418lb2t2 diff --git a/addons/dialogue_manager/components/download_update_panel.tscn b/addons/dialogue_manager/components/download_update_panel.tscn index f3f45236..540abd33 100644 --- a/addons/dialogue_manager/components/download_update_panel.tscn +++ b/addons/dialogue_manager/components/download_update_panel.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=3 format=3 uid="uid://qdxrxv3c3hxk"] -[ext_resource type="Script" path="res://addons/dialogue_manager/components/download_update_panel.gd" id="1_4tm1k"] +[ext_resource type="Script" uid="uid://kpwo418lb2t2" path="res://addons/dialogue_manager/components/download_update_panel.gd" id="1_4tm1k"] [ext_resource type="Texture2D" uid="uid://d3baj6rygkb3f" path="res://addons/dialogue_manager/assets/update.svg" id="2_4o2m6"] [node name="DownloadUpdatePanel" type="Control"] diff --git a/addons/dialogue_manager/components/editor_property/editor_property.gd.uid b/addons/dialogue_manager/components/editor_property/editor_property.gd.uid new file mode 100644 index 00000000..283cc439 --- /dev/null +++ b/addons/dialogue_manager/components/editor_property/editor_property.gd.uid @@ -0,0 +1 @@ +uid://nyypeje1a036 diff --git a/addons/dialogue_manager/components/editor_property/editor_property_control.gd b/addons/dialogue_manager/components/editor_property/editor_property_control.gd index ca52c79e..d063d0ea 100644 --- a/addons/dialogue_manager/components/editor_property/editor_property_control.gd +++ b/addons/dialogue_manager/components/editor_property/editor_property_control.gd @@ -63,7 +63,7 @@ func build_menu() -> void: func _on_new_dialog_file_selected(path: String) -> void: editor_plugin.main_view.new_file(path) is_waiting_for_file = false - if Engine.get_meta("DialogueCache").has_file(path): + if Engine.get_meta("DMCache").has_file(path): resource_changed.emit(load(path)) else: var next_resource: Resource = await editor_plugin.import_plugin.compiled_resource @@ -81,7 +81,7 @@ func _on_file_dialog_canceled() -> void: func _on_resource_button_pressed() -> void: if is_instance_valid(resource): - editor_plugin.get_editor_interface().call_deferred("edit_resource", resource) + EditorInterface.call_deferred("edit_resource", resource) else: build_menu() menu.position = get_viewport().position + Vector2i( @@ -112,7 +112,7 @@ func _on_menu_id_pressed(id: int) -> void: ITEM_QUICK_LOAD: quick_selected_file = "" - files_list.files = Engine.get_meta("DialogueCache").get_files() + files_list.files = Engine.get_meta("DMCache").get_files() if resource: files_list.select_file(resource.resource_path) quick_open_dialog.popup_centered() @@ -123,13 +123,13 @@ func _on_menu_id_pressed(id: int) -> void: open_dialog.popup_centered() ITEM_EDIT: - editor_plugin.get_editor_interface().call_deferred("edit_resource", resource) + EditorInterface.call_deferred("edit_resource", resource) ITEM_CLEAR: resource_changed.emit(null) ITEM_FILESYSTEM: - var file_system = editor_plugin.get_editor_interface().get_file_system_dock() + var file_system = EditorInterface.get_file_system_dock() file_system.navigate_to_path(resource.resource_path) diff --git a/addons/dialogue_manager/components/editor_property/editor_property_control.gd.uid b/addons/dialogue_manager/components/editor_property/editor_property_control.gd.uid new file mode 100644 index 00000000..aab7d8d5 --- /dev/null +++ b/addons/dialogue_manager/components/editor_property/editor_property_control.gd.uid @@ -0,0 +1 @@ +uid://dooe2pflnqtve diff --git a/addons/dialogue_manager/components/editor_property/editor_property_control.tscn b/addons/dialogue_manager/components/editor_property/editor_property_control.tscn index 8adb6a1c..7cb02e89 100644 --- a/addons/dialogue_manager/components/editor_property/editor_property_control.tscn +++ b/addons/dialogue_manager/components/editor_property/editor_property_control.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=4 format=3 uid="uid://ycn6uaj7dsrh"] -[ext_resource type="Script" path="res://addons/dialogue_manager/components/editor_property/editor_property_control.gd" id="1_het12"] +[ext_resource type="Script" uid="uid://dooe2pflnqtve" path="res://addons/dialogue_manager/components/editor_property/editor_property_control.gd" id="1_het12"] [ext_resource type="PackedScene" uid="uid://b16uuqjuof3n5" path="res://addons/dialogue_manager/components/editor_property/resource_button.tscn" id="2_hh3d4"] [ext_resource type="PackedScene" uid="uid://dnufpcdrreva3" path="res://addons/dialogue_manager/components/files_list.tscn" id="3_l8fp6"] diff --git a/addons/dialogue_manager/components/editor_property/resource_button.gd.uid b/addons/dialogue_manager/components/editor_property/resource_button.gd.uid new file mode 100644 index 00000000..b1b9d269 --- /dev/null +++ b/addons/dialogue_manager/components/editor_property/resource_button.gd.uid @@ -0,0 +1 @@ +uid://damhqta55t67c diff --git a/addons/dialogue_manager/components/editor_property/resource_button.tscn b/addons/dialogue_manager/components/editor_property/resource_button.tscn index d68d8513..691e527b 100644 --- a/addons/dialogue_manager/components/editor_property/resource_button.tscn +++ b/addons/dialogue_manager/components/editor_property/resource_button.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=2 format=3 uid="uid://b16uuqjuof3n5"] -[ext_resource type="Script" path="res://addons/dialogue_manager/components/editor_property/resource_button.gd" id="1_7u2i7"] +[ext_resource type="Script" uid="uid://damhqta55t67c" path="res://addons/dialogue_manager/components/editor_property/resource_button.gd" id="1_7u2i7"] [node name="ResourceButton" type="Button"] offset_right = 8.0 diff --git a/addons/dialogue_manager/components/errors_panel.gd b/addons/dialogue_manager/components/errors_panel.gd index f3e294d9..0b72d376 100644 --- a/addons/dialogue_manager/components/errors_panel.gd +++ b/addons/dialogue_manager/components/errors_panel.gd @@ -59,7 +59,7 @@ func show_error() -> void: show() count_label.text = DialogueConstants.translate(&"n_of_n").format({ index = error_index + 1, total = errors.size() }) var error = errors[error_index] - error_button.text = DialogueConstants.translate(&"errors.line_and_message").format({ line = error.line_number + 1, column = error.column_number, message = DialogueConstants.get_error_message(error.error) }) + error_button.text = DialogueConstants.translate(&"errors.line_and_message").format({ line = error.line_number, column = error.column_number, message = DialogueConstants.get_error_message(error.error) }) if error.has("external_error"): error_button.text += " " + DialogueConstants.get_error_message(error.external_error) @@ -72,7 +72,7 @@ func _on_errors_panel_theme_changed() -> void: func _on_error_button_pressed() -> void: - emit_signal("error_pressed", errors[error_index].line_number, errors[error_index].column_number) + error_pressed.emit(errors[error_index].line_number, errors[error_index].column_number) func _on_previous_button_pressed() -> void: diff --git a/addons/dialogue_manager/components/errors_panel.gd.uid b/addons/dialogue_manager/components/errors_panel.gd.uid new file mode 100644 index 00000000..c305a807 --- /dev/null +++ b/addons/dialogue_manager/components/errors_panel.gd.uid @@ -0,0 +1 @@ +uid://d2l8nlb6hhrfp diff --git a/addons/dialogue_manager/components/errors_panel.tscn b/addons/dialogue_manager/components/errors_panel.tscn index 956552b1..0b653cca 100644 --- a/addons/dialogue_manager/components/errors_panel.tscn +++ b/addons/dialogue_manager/components/errors_panel.tscn @@ -1,8 +1,8 @@ [gd_scene load_steps=4 format=3 uid="uid://cs8pwrxr5vxix"] -[ext_resource type="Script" path="res://addons/dialogue_manager/components/errors_panel.gd" id="1_nfm3c"] +[ext_resource type="Script" uid="uid://d2l8nlb6hhrfp" path="res://addons/dialogue_manager/components/errors_panel.gd" id="1_nfm3c"] -[sub_resource type="Image" id="Image_wy5pj"] +[sub_resource type="Image" id="Image_w0gko"] data = { "data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 93, 93, 55, 255, 97, 97, 58, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 98, 98, 47, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 94, 94, 46, 255, 93, 93, 236, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0), "format": "RGBA8", @@ -12,7 +12,7 @@ data = { } [sub_resource type="ImageTexture" id="ImageTexture_s6fxl"] -image = SubResource("Image_wy5pj") +image = SubResource("Image_w0gko") [node name="ErrorsPanel" type="HBoxContainer"] visible = false diff --git a/addons/dialogue_manager/components/files_list.gd.uid b/addons/dialogue_manager/components/files_list.gd.uid new file mode 100644 index 00000000..2a1089a5 --- /dev/null +++ b/addons/dialogue_manager/components/files_list.gd.uid @@ -0,0 +1 @@ +uid://dqa4a4wwoo0aa diff --git a/addons/dialogue_manager/components/files_list.tscn b/addons/dialogue_manager/components/files_list.tscn index 9d6f8547..e135e608 100644 --- a/addons/dialogue_manager/components/files_list.tscn +++ b/addons/dialogue_manager/components/files_list.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=3 format=3 uid="uid://dnufpcdrreva3"] -[ext_resource type="Script" path="res://addons/dialogue_manager/components/files_list.gd" id="1_cytii"] +[ext_resource type="Script" uid="uid://dqa4a4wwoo0aa" path="res://addons/dialogue_manager/components/files_list.gd" id="1_cytii"] [ext_resource type="Texture2D" uid="uid://d3lr2uas6ax8v" path="res://addons/dialogue_manager/assets/icon.svg" id="2_3ijx1"] [node name="FilesList" type="VBoxContainer"] diff --git a/addons/dialogue_manager/components/find_in_files.gd b/addons/dialogue_manager/components/find_in_files.gd index 916b744a..2614ecaa 100644 --- a/addons/dialogue_manager/components/find_in_files.gd +++ b/addons/dialogue_manager/components/find_in_files.gd @@ -114,7 +114,7 @@ func find_in_files() -> Dictionary: var results: Dictionary = {} var q: String = input.text - var cache = Engine.get_meta("DialogueCache") + var cache = Engine.get_meta("DMCache") var file: FileAccess for path in cache.get_files(): var path_results: Array = [] diff --git a/addons/dialogue_manager/components/find_in_files.gd.uid b/addons/dialogue_manager/components/find_in_files.gd.uid new file mode 100644 index 00000000..380a4918 --- /dev/null +++ b/addons/dialogue_manager/components/find_in_files.gd.uid @@ -0,0 +1 @@ +uid://q368fmxxa8sd diff --git a/addons/dialogue_manager/components/find_in_files.tscn b/addons/dialogue_manager/components/find_in_files.tscn index 8aaef4b9..97fca248 100644 --- a/addons/dialogue_manager/components/find_in_files.tscn +++ b/addons/dialogue_manager/components/find_in_files.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=3 format=3 uid="uid://0n7hwviyyly4"] -[ext_resource type="Script" path="res://addons/dialogue_manager/components/find_in_files.gd" id="1_3xicy"] +[ext_resource type="Script" uid="uid://q368fmxxa8sd" path="res://addons/dialogue_manager/components/find_in_files.gd" id="1_3xicy"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_owohg"] bg_color = Color(0.266667, 0.278431, 0.352941, 0.243137) diff --git a/addons/dialogue_manager/components/parse_result.gd b/addons/dialogue_manager/components/parse_result.gd deleted file mode 100644 index d467cb9a..00000000 --- a/addons/dialogue_manager/components/parse_result.gd +++ /dev/null @@ -1,10 +0,0 @@ -class_name DialogueManagerParseResult extends RefCounted - -var imported_paths: PackedStringArray = [] -var using_states: PackedStringArray = [] -var titles: Dictionary = {} -var character_names: PackedStringArray = [] -var first_title: String = "" -var lines: Dictionary = {} -var errors: Array[Dictionary] = [] -var raw_text: String = "" diff --git a/addons/dialogue_manager/components/parser.gd b/addons/dialogue_manager/components/parser.gd deleted file mode 100644 index bb5bb968..00000000 --- a/addons/dialogue_manager/components/parser.gd +++ /dev/null @@ -1,1798 +0,0 @@ -@tool - -class_name DialogueManagerParser extends Object - - -const DialogueConstants = preload("../constants.gd") -const DialogueSettings = preload("../settings.gd") -const ResolvedLineData = preload("./resolved_line_data.gd") -const ResolvedTagData = preload("./resolved_tag_data.gd") -const DialogueManagerParseResult = preload("./parse_result.gd") - - -var IMPORT_REGEX: RegEx = RegEx.create_from_string("import \"(?<path>[^\"]+)\" as (?<prefix>[a-zA-Z_\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]+)") -var USING_REGEX: RegEx = RegEx.create_from_string("^using (?<state>.*)$") -var VALID_TITLE_REGEX: RegEx = RegEx.create_from_string("^[a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]+$") -var BEGINS_WITH_NUMBER_REGEX: RegEx = RegEx.create_from_string("^\\d") -var TRANSLATION_REGEX: RegEx = RegEx.create_from_string("\\[ID:(?<tr>.*?)\\]") -var TAGS_REGEX: RegEx = RegEx.create_from_string("\\[#(?<tags>.*?)\\]") -var MUTATION_REGEX: RegEx = RegEx.create_from_string("(?<keyword>do|do!|set) (?<mutation>.*)") -var CONDITION_REGEX: RegEx = RegEx.create_from_string("(if|elif|while|else if) (?<condition>.*)") -var WRAPPED_CONDITION_REGEX: RegEx = RegEx.create_from_string("\\[if (?<condition>.*)\\]") -var REPLACEMENTS_REGEX: RegEx = RegEx.create_from_string("{{(.*?)}}") -var GOTO_REGEX: RegEx = RegEx.create_from_string("=><? (?<jump_to_title>.*)") -var INDENT_REGEX: RegEx = RegEx.create_from_string("^\\t+") -var INLINE_RANDOM_REGEX: RegEx = RegEx.create_from_string("\\[\\[(?<options>.*?)\\]\\]") -var INLINE_CONDITIONALS_REGEX: RegEx = RegEx.create_from_string("\\[if (?<condition>.+?)\\](?<body>.*?)\\[\\/if\\]") - -var TOKEN_DEFINITIONS: Dictionary = { - DialogueConstants.TOKEN_FUNCTION: RegEx.create_from_string("^[a-zA-Z_\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]*\\("), - DialogueConstants.TOKEN_DICTIONARY_REFERENCE: RegEx.create_from_string("^[a-zA-Z_\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]*\\["), - DialogueConstants.TOKEN_PARENS_OPEN: RegEx.create_from_string("^\\("), - DialogueConstants.TOKEN_PARENS_CLOSE: RegEx.create_from_string("^\\)"), - DialogueConstants.TOKEN_BRACKET_OPEN: RegEx.create_from_string("^\\["), - DialogueConstants.TOKEN_BRACKET_CLOSE: RegEx.create_from_string("^\\]"), - DialogueConstants.TOKEN_BRACE_OPEN: RegEx.create_from_string("^\\{"), - DialogueConstants.TOKEN_BRACE_CLOSE: RegEx.create_from_string("^\\}"), - DialogueConstants.TOKEN_COLON: RegEx.create_from_string("^:"), - DialogueConstants.TOKEN_COMPARISON: RegEx.create_from_string("^(==|<=|>=|<|>|!=|in )"), - DialogueConstants.TOKEN_ASSIGNMENT: RegEx.create_from_string("^(\\+=|\\-=|\\*=|/=|=)"), - DialogueConstants.TOKEN_NUMBER: RegEx.create_from_string("^\\-?\\d+(\\.\\d+)?"), - DialogueConstants.TOKEN_OPERATOR: RegEx.create_from_string("^(\\+|\\-|\\*|/|%)"), - DialogueConstants.TOKEN_COMMA: RegEx.create_from_string("^,"), - DialogueConstants.TOKEN_DOT: RegEx.create_from_string("^\\."), - DialogueConstants.TOKEN_STRING: RegEx.create_from_string("^&?(\".*?\"|\'.*?\')"), - DialogueConstants.TOKEN_NOT: RegEx.create_from_string("^(not( |$)|!)"), - DialogueConstants.TOKEN_AND_OR: RegEx.create_from_string("^(and|or|&&|\\|\\|)( |$)"), - DialogueConstants.TOKEN_VARIABLE: RegEx.create_from_string("^[a-zA-Z_\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]*"), - DialogueConstants.TOKEN_COMMENT: RegEx.create_from_string("^#.*"), - DialogueConstants.TOKEN_CONDITION: RegEx.create_from_string("^(if|elif|else)"), - DialogueConstants.TOKEN_BOOL: RegEx.create_from_string("^(true|false)") -} - -var WEIGHTED_RANDOM_SIBLINGS_REGEX: RegEx = RegEx.create_from_string("^\\%(?<weight>[\\d.]+)?( \\[if (?<condition>.+?)\\])? ") - -var raw_lines: PackedStringArray = [] -var parent_stack: Array[String] = [] - -var parsed_lines: Dictionary = {} -var imported_paths: PackedStringArray = [] -var using_states: PackedStringArray = [] -var titles: Dictionary = {} -var character_names: PackedStringArray = [] -var first_title: String = "" -var errors: Array[Dictionary] = [] -var raw_text: String = "" - -var _imported_line_map: Dictionary = {} -var _imported_line_count: int = 0 - -var while_loopbacks: Array[String] = [] - - -## Parse some raw dialogue text and return a dictionary containing parse results -static func parse_string(string: String, path: String) -> DialogueManagerParseResult: - var parser = new() - var error: Error = parser.parse(string, path) - var data: DialogueManagerParseResult = parser.get_data() - parser.free() - - if error == OK: - return data - else: - return null - - -## Extract bbcode and other markers from a string -static func extract_markers_from_string(string: String) -> ResolvedLineData: - var parser = new() - var markers: ResolvedLineData = parser.extract_markers(string) - parser.free() - - return markers - - -## Parse some raw dialogue text and return a dictionary containing parse results -func parse(text: String, path: String) -> Error: - prepare(text, path) - raw_text = text - - # Parse all of the content - var known_translations = {} - - # Get list of known autoloads - var autoload_names: PackedStringArray = get_autoload_names() - - # Keep track of the last doc comment - var doc_comments: Array[String] = [] - - # Then parse all lines - for id in range(0, raw_lines.size()): - var raw_line: String = raw_lines[id] - - var line: Dictionary = { - id = str(id), - next_id = DialogueConstants.ID_NULL - } - - # Work out if we are inside a conditional or option or if we just - # indented back out of one - var indent_size: int = get_indent(raw_line) - if indent_size < parent_stack.size() and not is_line_empty(raw_line): - for _tab in range(0, parent_stack.size() - indent_size): - parent_stack.pop_back() - - # If we are indented then this line should know about its parent - if parent_stack.size() > 0: - line["parent_id"] = parent_stack.back() - - # Trim any indentation (now that we've calculated it) so we can check - # the begining of each line for its type - raw_line = raw_line.strip_edges(true, false) - - # Grab translations - var translation_key: String = extract_translation(raw_line) - if translation_key != "": - line["translation_key"] = translation_key - raw_line = raw_line.replace("[ID:%s]" % translation_key, "") - - # Check for each kind of line - - # Start shortcuts - if raw_line.begins_with("using "): - var using_match: RegExMatch = USING_REGEX.search(raw_line) - if "state" in using_match.names: - var using_state: String = using_match.strings[using_match.names.state].strip_edges() - if not using_state in autoload_names: - add_error(id, 0, DialogueConstants.ERR_UNKNOWN_USING) - elif not using_state in using_states: - using_states.append(using_state) - continue - - # Response - elif is_response_line(raw_line): - # Add any doc notes - line["notes"] = "\n".join(doc_comments) - doc_comments = [] - - parent_stack.append(str(id)) - line["type"] = DialogueConstants.TYPE_RESPONSE - - # Extract any #tags - var tag_data: ResolvedTagData = extract_tags(raw_line) - line["tags"] = tag_data.tags - raw_line = tag_data.line_without_tags - - if " [if " in raw_line: - line["condition"] = extract_condition(raw_line, true, indent_size) - if " =>" in raw_line: - line["next_id"] = extract_goto(raw_line) - if " =><" in raw_line: - # Because of when the return point needs to be known at runtime we need to split - # this line into two (otherwise the return point would be dependent on the balloon) - var goto_line: Dictionary = { - type = DialogueConstants.TYPE_GOTO, - next_id = extract_goto(raw_line), - next_id_after = find_next_line_after_responses(id), - is_snippet = true - } - parsed_lines[str(id) + ".1"] = goto_line - line["next_id"] = str(id) + ".1" - - # Make sure the added goto line can actually go to somewhere - if goto_line.next_id in [DialogueConstants.ID_ERROR, DialogueConstants.ID_ERROR_INVALID_TITLE, DialogueConstants.ID_ERROR_TITLE_HAS_NO_BODY]: - line["next_id"] = goto_line.next_id - - line["character"] = "" - line["character_replacements"] = [] as Array[Dictionary] - line["text"] = extract_response_prompt(raw_line) - - var previous_response_id = find_previous_response_id(id) - if parsed_lines.has(previous_response_id): - var previous_response = parsed_lines[previous_response_id] - # Add this response to the list on the first response so that it is the - # authority on what is in the list of responses - previous_response["responses"] = previous_response["responses"] + PackedStringArray([str(id)]) - else: - # No previous response so this is the first in the list - line["responses"] = PackedStringArray([str(id)]) - - line["next_id_after"] = find_next_line_after_responses(id) - - # If this response has no body then the next id is the next id after - if not line.has("next_id") or line.next_id == DialogueConstants.ID_NULL: - var next_nonempty_line_id = get_next_nonempty_line_id(id) - if next_nonempty_line_id != DialogueConstants.ID_NULL: - if get_indent(raw_lines[next_nonempty_line_id.to_int()]) <= indent_size: - line["next_id"] = line.next_id_after - else: - line["next_id"] = next_nonempty_line_id - - line["text_replacements"] = extract_dialogue_replacements(line.get("text"), indent_size + 2) - for replacement in line.text_replacements: - if replacement.has("error"): - add_error(id, replacement.index, replacement.error) - - # If this response has a character name in it then it will automatically be - # injected as a line of dialogue if the player selects it - var response_text: String = line.text.replace("\\:", "!ESCAPED_COLON!") - if ":" in response_text: - if DialogueSettings.get_setting("create_lines_for_responses_with_characters", true): - var first_child: Dictionary = { - type = DialogueConstants.TYPE_DIALOGUE, - next_id = line.next_id, - next_id_after = line.next_id_after, - text_replacements = line.text_replacements, - tags = line.tags, - translation_key = line.get("translation_key") - } - parse_response_character_and_text(id, response_text, first_child, indent_size, parsed_lines) - line["character"] = first_child.character - line["character_replacements"] = first_child.character_replacements - line["text"] = first_child.text - line["text_replacements"] = extract_dialogue_replacements(line.text, indent_size + 2) - line["translation_key"] = first_child.translation_key - parsed_lines[str(id) + ".2"] = first_child - line["next_id"] = str(id) + ".2" - else: - parse_response_character_and_text(id, response_text, line, indent_size, parsed_lines) - else: - line["text"] = response_text.replace("!ESCAPED_COLON!", ":") - - # Title - elif is_title_line(raw_line): - line["type"] = DialogueConstants.TYPE_TITLE - line["text"] = extract_title(raw_line) - # Titles can't have numbers as the first letter (unless they are external titles which get replaced with hashes) - if id >= _imported_line_count and BEGINS_WITH_NUMBER_REGEX.search(line.text): - add_error(id, 2, DialogueConstants.ERR_TITLE_BEGINS_WITH_NUMBER) - # Only import titles are allowed to have "/" in them - var valid_title = VALID_TITLE_REGEX.search(raw_line.replace("/", "").substr(raw_line.find("~ ") + 2).strip_edges()) - if not valid_title: - add_error(id, 2, DialogueConstants.ERR_TITLE_INVALID_CHARACTERS) - - # Condition - elif is_condition_line(raw_line, false): - parent_stack.append(str(id)) - line["type"] = DialogueConstants.TYPE_CONDITION - line["condition"] = extract_condition(raw_line, false, indent_size) - line["next_id_after"] = find_next_line_after_conditions(id) - var next_sibling_id = find_next_condition_sibling(id) - line["next_conditional_id"] = next_sibling_id if is_valid_id(next_sibling_id) else line.next_id_after - - elif is_condition_line(raw_line, true): - parent_stack.append(str(id)) - line["type"] = DialogueConstants.TYPE_CONDITION - line["next_id_after"] = find_next_line_after_conditions(id) - line["next_conditional_id"] = line["next_id_after"] - - elif is_while_condition_line(raw_line): - parent_stack.append(str(id)) - line["type"] = DialogueConstants.TYPE_CONDITION - line["condition"] = extract_condition(raw_line, false, indent_size) - line["next_id_after"] = find_next_line_after_conditions(id) - while_loopbacks.append(find_last_line_within_conditions(id)) - line["next_conditional_id"] = line["next_id_after"] - - # Mutation - elif is_mutation_line(raw_line): - line["type"] = DialogueConstants.TYPE_MUTATION - line["mutation"] = extract_mutation(raw_line) - - # Goto - elif is_goto_line(raw_line): - line["type"] = DialogueConstants.TYPE_GOTO - - if raw_line.begins_with("%"): - apply_weighted_random(id, raw_line, indent_size, line) - - line["next_id"] = extract_goto(raw_line) - if is_goto_snippet_line(raw_line): - line["is_snippet"] = true - line["next_id_after"] = get_line_after_line(id, indent_size, line) - else: - line["is_snippet"] = false - - # Nested dialogue - elif is_nested_dialogue_line(raw_line, parsed_lines, raw_lines, indent_size): - var parent_line: Dictionary = parsed_lines.values().back() - var parent_indent_size: int = get_indent(raw_lines[parent_line.id.to_int()]) - var should_update_translation_key: bool = parent_line.translation_key == parent_line.text - var suffix: String = raw_line.strip_edges(true, false) - if suffix == "": - suffix = " " - parent_line["text"] += "\n" + suffix - parent_line["text_replacements"] = extract_dialogue_replacements(parent_line.text, parent_line.character.length() + 2 + parent_indent_size) - for replacement in parent_line.text_replacements: - if replacement.has("error"): - add_error(id, replacement.index, replacement.error) - - if should_update_translation_key: - parent_line["translation_key"] = parent_line.text - - parent_line["next_id"] = get_line_after_line(id, parent_indent_size, parent_line) - - # Ignore this line when checking for indent errors - remove_error(parent_line.id.to_int(), DialogueConstants.ERR_INVALID_INDENTATION) - - var next_line = raw_lines[parent_line.next_id.to_int()] - if not is_dialogue_line(next_line) and get_indent(next_line) >= indent_size: - add_error(parent_line.next_id.to_int(), indent_size, DialogueConstants.ERR_INVALID_INDENTATION) - - continue - - elif raw_line.strip_edges().begins_with("##"): - doc_comments.append(raw_line.replace("##", "").strip_edges()) - continue - - elif is_line_empty(raw_line) or is_import_line(raw_line): - continue - - # Regular dialogue - else: - # Remove escape character - if raw_line.begins_with("\\using"): raw_line = raw_line.substr(1) - if raw_line.begins_with("\\if"): raw_line = raw_line.substr(1) - if raw_line.begins_with("\\elif"): raw_line = raw_line.substr(1) - if raw_line.begins_with("\\else"): raw_line = raw_line.substr(1) - if raw_line.begins_with("\\while"): raw_line = raw_line.substr(1) - if raw_line.begins_with("\\-"): raw_line = raw_line.substr(1) - if raw_line.begins_with("\\~"): raw_line = raw_line.substr(1) - if raw_line.begins_with("\\=>"): raw_line = raw_line.substr(1) - - # Check for jumps - if " => " in raw_line: - line["next_id"] = extract_goto(raw_line) - raw_line = raw_line.split(" => ")[0] - - # Add any doc notes - line["notes"] = "\n".join(doc_comments) - doc_comments = [] - - # Work out any weighted random siblings - if raw_line.begins_with("%"): - apply_weighted_random(id, raw_line, indent_size, line) - raw_line = WEIGHTED_RANDOM_SIBLINGS_REGEX.sub(raw_line, "") - - line["type"] = DialogueConstants.TYPE_DIALOGUE - - # Extract any tags before we process the line - var tag_data: ResolvedTagData = extract_tags(raw_line) - line["tags"] = tag_data.tags - raw_line = tag_data.line_without_tags - - var l = raw_line.replace("\\:", "!ESCAPED_COLON!") - - if ":" in l: - var bits = Array(l.strip_edges().split(":")) - line["character"] = bits.pop_front().strip_edges() - if not line["character"] in character_names: - character_names.append(line["character"]) - # You can use variables in the character's name - line["character_replacements"] = extract_dialogue_replacements(line.character, indent_size) - for replacement in line.character_replacements: - if replacement.has("error"): - add_error(id, replacement.index, replacement.error) - line["text"] = ":".join(bits).replace("!ESCAPED_COLON!", ":") - else: - line["character"] = "" - line["character_replacements"] = [] as Array[Dictionary] - line["text"] = l.replace("!ESCAPED_COLON!", ":") - - line["text_replacements"] = extract_dialogue_replacements(line.text, line.character.length() + 2 + indent_size) - for replacement in line.text_replacements: - if replacement.has("error"): - add_error(id, replacement.index, replacement.error) - - # Unescape any newlines - line["text"] = line.text.replace("\\n", "\n").strip_edges() - - # Work out where to go after this line - if line.next_id == DialogueConstants.ID_NULL: - line["next_id"] = get_line_after_line(id, indent_size, line) - - # Check for duplicate translation keys - if line.type in [DialogueConstants.TYPE_DIALOGUE, DialogueConstants.TYPE_RESPONSE]: - if line.has("translation_key"): - if known_translations.has(line.translation_key) and known_translations.get(line.translation_key) != line.text: - add_error(id, indent_size, DialogueConstants.ERR_DUPLICATE_ID) - else: - known_translations[line.translation_key] = line.text - else: - # Default translations key - if DialogueSettings.get_setting("missing_translations_are_errors", false): - add_error(id, indent_size, DialogueConstants.ERR_MISSING_ID) - else: - line["translation_key"] = line.text - - ## Error checking - - # Can't find goto - var jump_index: int = raw_line.find("=>") - match line.next_id: - DialogueConstants.ID_ERROR: - add_error(id, jump_index, DialogueConstants.ERR_UNKNOWN_TITLE) - DialogueConstants.ID_ERROR_INVALID_TITLE: - add_error(id, jump_index, DialogueConstants.ERR_INVALID_TITLE_REFERENCE) - DialogueConstants.ID_ERROR_TITLE_HAS_NO_BODY: - add_error(id, jump_index, DialogueConstants.ERR_TITLE_REFERENCE_HAS_NO_CONTENT) - - # Line after condition isn't indented once to the right - if line.type == DialogueConstants.TYPE_CONDITION: - if is_valid_id(line.next_id): - var next_line: String = raw_lines[line.next_id.to_int()] - var next_indent: int = get_indent(next_line) - if next_indent != indent_size + 1: - add_error(line.next_id.to_int(), next_indent, DialogueConstants.ERR_INVALID_INDENTATION) - else: - add_error(id, indent_size, DialogueConstants.ERR_INVALID_CONDITION_INDENTATION) - - # Line after normal line is indented to the right - elif line.type in [ - DialogueConstants.TYPE_TITLE, - DialogueConstants.TYPE_DIALOGUE, - DialogueConstants.TYPE_MUTATION, - ] and is_valid_id(line.next_id): - var next_line = raw_lines[line.next_id.to_int()] - if next_line != null and get_indent(next_line) > indent_size: - add_error(id, indent_size, DialogueConstants.ERR_INVALID_INDENTATION) - - # Parsing condition failed - if line.has("condition") and line.condition.has("error"): - add_error(id, line.condition.index, line.condition.error) - - # Parsing mutation failed - elif line.has("mutation") and line.mutation.has("error"): - add_error(id, line.mutation.index, line.mutation.error) - - # Line failed to parse at all - if line.get("type") == DialogueConstants.TYPE_UNKNOWN: - add_error(id, 0, DialogueConstants.ERR_UNKNOWN_LINE_SYNTAX) - - # If there are no titles then use the first actual line - if first_title == "" and not is_import_line(raw_line): - first_title = str(id) - - # If this line is the last line of a while loop, edit the id of its next line - if str(id) in while_loopbacks: - if is_goto_snippet_line(raw_line): - line["next_id_after"] = line["parent_id"] - elif is_condition_line(raw_line, true) or is_while_condition_line(raw_line): - line["next_conditional_id"] = line["parent_id"] - line["next_id_after"] = line["parent_id"] - elif is_goto_line(raw_line) or is_title_line(raw_line): - pass - else: - line["next_id"] = line["parent_id"] - - # Done! - parsed_lines[str(id)] = line - - # Assume the last line ends the dialogue - var last_line: Dictionary = parsed_lines.values()[parsed_lines.values().size() - 1] - if last_line.next_id == "": - last_line.next_id = DialogueConstants.ID_END - - if errors.size() > 0: - return ERR_PARSE_ERROR - - return OK - - -func get_data() -> DialogueManagerParseResult: - var data: DialogueManagerParseResult = DialogueManagerParseResult.new() - data.imported_paths = imported_paths - data.using_states = using_states - data.titles = titles - data.character_names = character_names - data.first_title = first_title - data.lines = parsed_lines - data.errors = errors - data.raw_text = raw_text - return data - - -## Get the last parse errors -func get_errors() -> Array[Dictionary]: - return errors - - -## Prepare the parser by collecting all lines and titles -func prepare(text: String, path: String, include_imported_titles_hashes: bool = true) -> void: - using_states = [] - errors = [] - imported_paths = [] - _imported_line_map = {} - while_loopbacks = [] - titles = {} - character_names = [] - first_title = "" - raw_lines = text.split("\n") - - # Work out imports - var known_imports: Dictionary = {} - - # Include the base file path so that we can get around circular dependencies - known_imports[path.hash()] = "." - - var imported_titles: Dictionary = {} - for id in range(0, raw_lines.size()): - var line = raw_lines[id] - if is_import_line(line): - var import_data = extract_import_path_and_name(line) - var import_hash: int = import_data.path.hash() - if import_data.size() > 0: - # Keep track of titles so we can add imported ones later - if str(import_hash) in imported_titles.keys(): - add_error(id, 0, DialogueConstants.ERR_FILE_ALREADY_IMPORTED) - if import_data.prefix in imported_titles.values(): - add_error(id, 0, DialogueConstants.ERR_DUPLICATE_IMPORT_NAME) - imported_titles[str(import_hash)] = import_data.prefix - - # Import the file content - if not known_imports.has(import_hash): - var error: Error = import_content(import_data.path, import_data.prefix, _imported_line_map, known_imports) - if error != OK: - add_error(id, 0, error) - - # Make a map so we can refer compiled lines to where they were imported from - if not _imported_line_map.has(import_hash): - _imported_line_map[import_hash] = { - hash = import_hash, - imported_on_line_number = id, - from_line = 0, - to_line = 0 - } - - var imported_content: String = "" - var cummulative_line_number: int = 0 - for item in _imported_line_map.values(): - item["from_line"] = cummulative_line_number - if known_imports.has(item.hash): - cummulative_line_number += known_imports[item.hash].split("\n").size() - item["to_line"] = cummulative_line_number - if known_imports.has(item.hash): - imported_content += known_imports[item.hash] + "\n" - - _imported_line_count = cummulative_line_number + 1 - - # Join it with the actual content - raw_lines = (imported_content + "\n" + text).split("\n") - - # Find all titles first - for id in range(0, raw_lines.size()): - if raw_lines[id].strip_edges().begins_with("~ "): - var title: String = extract_title(raw_lines[id]) - if title == "": - add_error(id, 2, DialogueConstants.ERR_EMPTY_TITLE) - elif titles.has(title): - add_error(id, 2, DialogueConstants.ERR_DUPLICATE_TITLE) - else: - var next_nonempty_line_id: String = get_next_nonempty_line_id(id) - if next_nonempty_line_id != DialogueConstants.ID_NULL: - titles[title] = next_nonempty_line_id - if "/" in title: - if include_imported_titles_hashes == false: - titles.erase(title) - var bits: PackedStringArray = title.split("/") - if imported_titles.has(bits[0]): - title = imported_titles[bits[0]] + "/" + bits[1] - titles[title] = next_nonempty_line_id - elif first_title == "": - first_title = next_nonempty_line_id - else: - titles[title] = DialogueConstants.ID_ERROR_TITLE_HAS_NO_BODY - - -func add_error(line_number: int, column_number: int, error: int) -> void: - # See if the error was in an imported file - for item in _imported_line_map.values(): - if line_number < item.to_line: - errors.append({ - line_number = item.imported_on_line_number, - column_number = 0, - error = DialogueConstants.ERR_ERRORS_IN_IMPORTED_FILE, - external_error = error, - external_line_number = line_number - }) - return - - # Otherwise, it's in this file - errors.append({ - line_number = line_number - _imported_line_count, - column_number = column_number, - error = error - }) - - -func remove_error(line_number: int, error: int) -> void: - for i in range(errors.size() - 1, -1, -1): - var err = errors[i] - var is_native_error = err.line_number == line_number - _imported_line_count and err.error == error - var is_external_error = err.get("external_line_number") == line_number and err.get("external_error") == error - if is_native_error or is_external_error: - errors.remove_at(i) - return - - -func is_import_line(line: String) -> bool: - return line.begins_with("import ") and " as " in line - - -func is_title_line(line: String) -> bool: - return line.strip_edges(true, false).begins_with("~ ") - - -func is_condition_line(line: String, include_else: bool = true) -> bool: - line = line.strip_edges(true, false) - if line.begins_with("if ") or line.begins_with("elif ") or line.begins_with("else if"): return true - if include_else and line.begins_with("else"): return true - return false - -func is_while_condition_line(line: String) -> bool: - line = line.strip_edges(true, false) - if line.begins_with("while "): return true - return false - - -func is_mutation_line(line: String) -> bool: - line = line.strip_edges(true, false) - return line.begins_with("do ") or line.begins_with("do! ") or line.begins_with("set ") - - -func is_goto_line(line: String) -> bool: - line = line.strip_edges(true, false) - line = WEIGHTED_RANDOM_SIBLINGS_REGEX.sub(line, "") - return line.begins_with("=> ") or line.begins_with("=>< ") - - -func is_goto_snippet_line(line: String) -> bool: - line = WEIGHTED_RANDOM_SIBLINGS_REGEX.sub(line.strip_edges(), "") - return line.begins_with("=>< ") - - -func is_nested_dialogue_line(raw_line: String, parsed_lines: Dictionary, raw_lines: PackedStringArray, indent_size: int) -> bool: - if parsed_lines.values().is_empty(): return false - if raw_line.strip_edges().begins_with("#"): return false - - var parent_line: Dictionary = parsed_lines.values().back() - if parent_line.type != DialogueConstants.TYPE_DIALOGUE: return false - if get_indent(raw_lines[parent_line.id.to_int()]) >= indent_size: return false - return true - - -func is_dialogue_line(line: String) -> bool: - if line == null: return false - if is_response_line(line): return false - if is_title_line(line): return false - if is_condition_line(line, true): return false - if is_mutation_line(line): return false - if is_goto_line(line): return false - return true - - -func is_response_line(line: String) -> bool: - return line.strip_edges(true, false).begins_with("- ") - - -func is_valid_id(id: String) -> bool: - return false if id in [DialogueConstants.ID_NULL, DialogueConstants.ID_ERROR, DialogueConstants.ID_END_CONVERSATION] else true - - -func is_line_empty(line: String) -> bool: - line = line.strip_edges() - - if line == "": return true - if line == "endif": return true - if line.begins_with("#"): return true - - return false - - -func get_line_after_line(id: int, indent_size: int, line: Dictionary) -> String: - # Unless the next line is an outdent we can assume it comes next - var next_nonempty_line_id = get_next_nonempty_line_id(id) - - if next_nonempty_line_id != DialogueConstants.ID_NULL and indent_size <= get_indent(raw_lines[next_nonempty_line_id.to_int()]): - return next_nonempty_line_id - # Otherwise, we grab the ID from the parents next ID after children - elif line.has("parent_id") and parsed_lines.has(line.parent_id): - return parsed_lines[line.parent_id].next_id_after - else: - return DialogueConstants.ID_NULL - - -func get_indent(line: String) -> int: - var tabs: RegExMatch = INDENT_REGEX.search(line) - if tabs: - return tabs.get_string().length() - else: - return 0 - - -func get_next_nonempty_line_id(line_number: int) -> String: - for i in range(line_number + 1, raw_lines.size()): - if not is_line_empty(raw_lines[i]): - return str(i) - return DialogueConstants.ID_NULL - - -func find_previous_response_id(line_number: int) -> String: - var line = raw_lines[line_number] - var indent_size = get_indent(line) - - # Look back up the list to find the previous response - var last_found_response_id: String = str(line_number) - - for i in range(line_number - 1, -1, -1): - line = raw_lines[i] - - if is_line_empty(line): continue - - # If its a response at the same indent level then its a match - elif get_indent(line) == indent_size: - if line.strip_edges().begins_with("- "): - last_found_response_id = str(i) - else: - break - elif get_indent(line) < indent_size: - break - - # Return the most relevant ID - return last_found_response_id - - -func apply_weighted_random(id: int, raw_line: String, indent_size: int, line: Dictionary) -> void: - var weight: float = 1 - var found = WEIGHTED_RANDOM_SIBLINGS_REGEX.search(raw_line) - var condition: Dictionary = {} - if found: - if found.names.has("weight"): - weight = found.strings[found.names.weight].to_float() - if found.names.has("condition"): - condition = extract_condition(raw_line, true, indent_size) - - # Look back up the list to find the first weighted random line in this group - var original_random_line: Dictionary = {} - for i in range(id, 0, -1): - # Ignore doc comment lines - if raw_lines[i].strip_edges().begins_with("##"): - continue - # Lines that aren't prefixed with the random token are a dead end - if not raw_lines[i].strip_edges().begins_with("%") or get_indent(raw_lines[i]) != indent_size: - break - # Make sure we group random dialogue and random lines separately - elif WEIGHTED_RANDOM_SIBLINGS_REGEX.sub(raw_line.strip_edges(), "").begins_with("=") != WEIGHTED_RANDOM_SIBLINGS_REGEX.sub(raw_lines[i].strip_edges(), "").begins_with("="): - break - # Otherwise we've found the origin - elif parsed_lines.has(str(i)) and parsed_lines[str(i)].has("siblings"): - original_random_line = parsed_lines[str(i)] - break - - # Attach it to the original random line and work out where to go after the line - if original_random_line.size() > 0: - original_random_line["siblings"] += [{ weight = weight, id = str(id), condition = condition }] - if original_random_line.type != DialogueConstants.TYPE_GOTO: - # Update the next line for all siblings (not goto lines, though, they manage their - # own next ID) - original_random_line["next_id"] = get_line_after_line(id, indent_size, line) - for sibling in original_random_line["siblings"]: - if sibling.id in parsed_lines: - parsed_lines[sibling.id]["next_id"] = original_random_line["next_id"] - elif original_random_line.has("next_id_after"): - original_random_line["next_id_after"] = get_line_after_line(id, indent_size, line) - for sibling in original_random_line["siblings"]: - if sibling.id in parsed_lines: - parsed_lines[sibling.id]["next_id_after"] = original_random_line["next_id_after"] - - line["next_id"] = original_random_line.next_id - # Or set up this line as the original - else: - line["siblings"] = [{ weight = weight, id = str(id), condition = condition }] - line["next_id"] = get_line_after_line(id, indent_size, line) - - if line.next_id == DialogueConstants.ID_NULL: - line["next_id"] = DialogueConstants.ID_END - - -func find_next_condition_sibling(line_number: int) -> String: - var line = raw_lines[line_number] - var expected_indent = get_indent(line) - - # Look down the list and find an elif or else at the same indent level - for i in range(line_number + 1, raw_lines.size()): - line = raw_lines[i] - if is_line_empty(line): continue - - var l = line.strip_edges() - if l.begins_with("~ "): - return DialogueConstants.ID_END_CONVERSATION - - elif get_indent(line) < expected_indent: - return DialogueConstants.ID_NULL - - elif get_indent(line) == expected_indent: - # Found an if, which begins a different block - if l.begins_with("if"): - return DialogueConstants.ID_NULL - - # Found what we're looking for - elif (l.begins_with("elif ") or l.begins_with("else")): - return str(i) - - return DialogueConstants.ID_NULL - - -func find_next_line_after_conditions(line_number: int) -> String: - var line = raw_lines[line_number] - var expected_indent = get_indent(line) - - # Look down the list for the first non condition line at the same or less indent level - for i in range(line_number + 1, raw_lines.size()): - line = raw_lines[i] - - if is_line_empty(line): continue - - var line_indent = get_indent(line) - line = line.strip_edges() - - if line_indent > expected_indent: - continue - - elif line_indent == expected_indent: - if line.begins_with("elif ") or line.begins_with("else"): - continue - else: - return str(i) - - elif line_indent < expected_indent: - # We have to check the parent of this block - for p in range(line_number - 1, -1, -1): - line = raw_lines[p] - - if is_line_empty(line): continue - - line_indent = get_indent(line) - if line_indent < expected_indent: - return parsed_lines[str(p)].get("next_id_after", DialogueConstants.ID_NULL) - - return DialogueConstants.ID_END_CONVERSATION - - -func find_last_line_within_conditions(line_number: int) -> String: - var line = raw_lines[line_number] - var expected_indent = get_indent(line) - - var candidate = DialogueConstants.ID_NULL - - # Look down the list for the last line that has an indent level 1 more than this line - # Ending the search when you find a line the same or less indent level - for i in range(line_number + 1, raw_lines.size()): - line = raw_lines[i] - - if is_line_empty(line): continue - - var line_indent = get_indent(line) - line = line.strip_edges() - - if line_indent > expected_indent + 1: - continue - elif line_indent == (expected_indent + 1): - candidate = i - else: - break - - return str(candidate) - -func find_next_line_after_responses(line_number: int) -> String: - var line = raw_lines[line_number] - var expected_indent = get_indent(line) - - # Find the first line after this one that has a smaller indent that isn't another option - # If we hit the eof then we give up - for i in range(line_number + 1, raw_lines.size()): - line = raw_lines[i] - - if is_line_empty(line): continue - - var indent = get_indent(line) - - line = line.strip_edges() - - # We hit a title so the next line is a new start - if is_title_line(line): - return get_next_nonempty_line_id(i) - - # Another option - elif line.begins_with("- "): - if indent == expected_indent: - # ...at the same level so we continue - continue - elif indent < expected_indent: - # ...outdented so check the previous parent - var previous_parent = parent_stack[parent_stack.size() - 2] - if parsed_lines.has(str(previous_parent)): - return parsed_lines[str(previous_parent)].next_id_after - else: - return DialogueConstants.ID_NULL - - # We're at the end of a conditional so jump back up to see what's after it - elif line.begins_with("elif ") or line.begins_with("else"): - for p in range(line_number - 1, -1, -1): - line = raw_lines[p] - - if is_line_empty(line): continue - - var line_indent = get_indent(line) - if line_indent < expected_indent: - return parsed_lines[str(p)].next_id_after - - # Otherwise check the indent for an outdent - else: - line_number = i - line = raw_lines[line_number] - if get_indent(line) <= expected_indent: - return str(line_number) - - # EOF so it's also the end of a block - return DialogueConstants.ID_END - - -## Get the names of any autoloads in the project -func get_autoload_names() -> PackedStringArray: - var autoloads: PackedStringArray = [] - - var project = ConfigFile.new() - project.load("res://project.godot") - if project.has_section("autoload"): - return Array(project.get_section_keys("autoload")).filter(func(key): return key != "DialogueManager") - - return autoloads - - -## Import content from another dialogue file or return an ERR -func import_content(path: String, prefix: String, imported_line_map: Dictionary, known_imports: Dictionary) -> Error: - if FileAccess.file_exists(path): - var file = FileAccess.open(path, FileAccess.READ) - var content: PackedStringArray = file.get_as_text().split("\n") - - var imported_titles: Dictionary = {} - - for index in range(0, content.size()): - var line = content[index] - if is_import_line(line): - var import = extract_import_path_and_name(line) - if import.size() > 0: - if not known_imports.has(import.path.hash()): - # Add an empty record into the keys just so we don't end up with cyclic dependencies - known_imports[import.path.hash()] = "" - if import_content(import.path, import.prefix, imported_line_map, known_imports) != OK: - return ERR_LINK_FAILED - - if not imported_line_map.has(import.path.hash()): - # Make a map so we can refer compiled lines to where they were imported from - imported_line_map[import.path.hash()] = { - hash = import.path.hash(), - imported_on_line_number = index, - from_line = 0, - to_line = 0 - } - - imported_titles[import.prefix] = import.path.hash() - - var origin_hash: int = -1 - for hash_value in known_imports.keys(): - if known_imports[hash_value] == ".": - origin_hash = hash_value - - # Replace any titles or jump points with references to the files they point to (event if they point to their own file) - for i in range(0, content.size()): - var line = content[i] - if is_title_line(line): - var title = extract_title(line) - if "/" in line: - var bits = title.split("/") - content[i] = "~ %s/%s" % [imported_titles[bits[0]], bits[1]] - else: - content[i] = "~ %s/%s" % [str(path.hash()), title] - - elif "=>< " in line: - var jump: String = line.substr(line.find("=>< ") + "=>< ".length()).strip_edges() - if "/" in jump: - var bits: PackedStringArray = jump.split("/") - var title_hash: int = imported_titles[bits[0]] - if title_hash == origin_hash: - content[i] = "%s=>< %s" % [line.split("=>< ")[0], bits[1]] - else: - content[i] = "%s=>< %s/%s" % [line.split("=>< ")[0], title_hash, bits[1]] - - elif not jump in ["END", "END!"]: - content[i] = "%s=>< %s/%s" % [line.split("=>< ")[0], str(path.hash()), jump] - - elif "=> " in line: - var jump: String = line.substr(line.find("=> ") + "=> ".length()).strip_edges() - if "/" in jump: - var bits: PackedStringArray = jump.split("/") - var title_hash: int = imported_titles[bits[0]] - if title_hash == origin_hash: - content[i] = "%s=> %s" % [line.split("=> ")[0], bits[1]] - else: - content[i] = "%s=> %s/%s" % [line.split("=> ")[0], title_hash, bits[1]] - - elif not jump in ["END", "END!"]: - content[i] = "%s=> %s/%s" % [line.split("=> ")[0], str(path.hash()), jump] - - imported_paths.append(path) - known_imports[path.hash()] = "\n".join(content) + "\n=> END\n" - return OK - else: - return ERR_FILE_NOT_FOUND - - -func extract_import_path_and_name(line: String) -> Dictionary: - var found: RegExMatch = IMPORT_REGEX.search(line) - if found: - return { - path = found.strings[found.names.path], - prefix = found.strings[found.names.prefix] - } - else: - return {} - - -func extract_title(line: String) -> String: - return line.substr(line.find("~ ") + 2).strip_edges() - - -func extract_translation(line: String) -> String: - # Find a static translation key, eg. [ID:something] - var found: RegExMatch = TRANSLATION_REGEX.search(line) - if found: - return found.strings[found.names.tr] - else: - return "" - - -func extract_response_prompt(line: String) -> String: - # Find just the text prompt from a response, ignoring any conditions or gotos - line = line.substr(2) - if " [if " in line: - line = line.substr(0, line.find(" [if ")) - if " =>" in line: - line = line.substr(0, line.find(" =>")) - - # Without the translation key if there is one - var translation_key: String = extract_translation(line) - if translation_key: - line = line.replace("[ID:%s]" % translation_key, "") - - return line.replace("\\n", "\n").strip_edges() - - -func parse_response_character_and_text(id: int, text: String, line: Dictionary, indent_size: int, parsed_lines: Dictionary) -> void: - var bits = Array(text.strip_edges().split(": ")) - line["character"] = bits.pop_front().strip_edges() - line["character_replacements"] = extract_dialogue_replacements(line.character, line.character.length() + 2 + indent_size) - for replacement in line.character_replacements: - if replacement.has("error"): - add_error(id, replacement.index, replacement.error) - - if not line["character"] in character_names: - character_names.append(line["character"]) - - line["text"] = ":".join(bits).replace("!ESCAPED_COLON!", ":").strip_edges() - - if line.get("translation_key", null) == null: - line["translation_key"] = line.text - - -func extract_mutation(line: String) -> Dictionary: - var found: RegExMatch = MUTATION_REGEX.search(line) - - if not found: - return { - index = 0, - error = DialogueConstants.ERR_INCOMPLETE_EXPRESSION - } - - if found.names.has("mutation"): - var expression: Array = tokenise(found.strings[found.names.mutation], DialogueConstants.TYPE_MUTATION, found.get_start("mutation")) - if expression.size() == 0: - return { - index = found.get_start("mutation"), - error = DialogueConstants.ERR_INCOMPLETE_EXPRESSION - } - elif expression[0].type == DialogueConstants.TYPE_ERROR: - return { - index = expression[0].index, - error = expression[0].value - } - else: - return { - expression = expression, - is_blocking = not "!" in found.strings[found.names.keyword] - } - - else: - return { - index = found.get_start(), - error = DialogueConstants.ERR_INCOMPLETE_EXPRESSION - } - - -func extract_condition(raw_line: String, is_wrapped: bool, index: int) -> Dictionary: - var condition: Dictionary = {} - - var regex: RegEx = WRAPPED_CONDITION_REGEX if is_wrapped else CONDITION_REGEX - var found: RegExMatch = regex.search(raw_line) - - if found == null: - return { - index = 0, - error = DialogueConstants.ERR_INCOMPLETE_EXPRESSION - } - - var raw_condition: String = found.strings[found.names.condition] - var expression: Array = tokenise(raw_condition, DialogueConstants.TYPE_CONDITION, index + found.get_start("condition")) - - if expression.size() == 0: - return { - index = index + found.get_start("condition"), - error = DialogueConstants.ERR_INCOMPLETE_EXPRESSION - } - elif expression[0].type == DialogueConstants.TYPE_ERROR: - return { - index = expression[0].index, - error = expression[0].value - } - else: - return { - expression = expression - } - - -func extract_dialogue_replacements(text: String, index: int) -> Array[Dictionary]: - var founds: Array[RegExMatch] = REPLACEMENTS_REGEX.search_all(text) - - if founds == null or founds.size() == 0: - return [] - - var replacements: Array[Dictionary] = [] - for found in founds: - var replacement: Dictionary = {} - var value_in_text: String = found.strings[1] - var expression: Array = tokenise(value_in_text, DialogueConstants.TYPE_DIALOGUE, index + found.get_start(1)) - if expression.size() == 0: - replacement = { - index = index + found.get_start(1), - error = DialogueConstants.ERR_INCOMPLETE_EXPRESSION - } - elif expression[0].type == DialogueConstants.TYPE_ERROR: - replacement = { - index = expression[0].index, - error = expression[0].value - } - else: - replacement = { - value_in_text = "{{%s}}" % value_in_text, - expression = expression - } - replacements.append(replacement) - - return replacements - - -func extract_goto(line: String) -> String: - var found: RegExMatch = GOTO_REGEX.search(line) - - if found == null: return DialogueConstants.ID_ERROR - - var title: String = found.strings[found.names.jump_to_title].strip_edges() - - if " " in title or title == "": - return DialogueConstants.ID_ERROR_INVALID_TITLE - - # "=> END!" means end the conversation - if title == "END!": - return DialogueConstants.ID_END_CONVERSATION - # "=> END" means end the current title (and go back to the previous one if there is one - # in the stack) - elif title == "END": - return DialogueConstants.ID_END - - elif titles.has(title): - return titles.get(title) - else: - return DialogueConstants.ID_ERROR - - -func extract_tags(line: String) -> ResolvedTagData: - var resolved_tags: PackedStringArray = [] - var tag_matches: Array[RegExMatch] = TAGS_REGEX.search_all(line) - for tag_match in tag_matches: - line = line.replace(tag_match.get_string(), "") - var tags = tag_match.get_string().replace("[#", "").replace("]", "").replace(", ", ",").split(",") - for tag in tags: - tag = tag.replace("#", "") - if not tag in resolved_tags: - resolved_tags.append(tag) - - return ResolvedTagData.new({ - tags = resolved_tags, - line_without_tags = line - }) - - -func extract_markers(line: String) -> ResolvedLineData: - var text: String = line - var pauses: Dictionary = {} - var speeds: Dictionary = {} - var mutations: Array[Array] = [] - var bbcodes: Array = [] - var time: String = "" - - # Remove any escaped brackets (ie. "\[") - var escaped_open_brackets: PackedInt32Array = [] - var escaped_close_brackets: PackedInt32Array = [] - for i in range(0, text.length() - 1): - if text.substr(i, 2) == "\\[": - text = text.substr(0, i) + "!" + text.substr(i + 2) - escaped_open_brackets.append(i) - elif text.substr(i, 2) == "\\]": - text = text.substr(0, i) + "!" + text.substr(i + 2) - escaped_close_brackets.append(i) - - # Extract all of the BB codes so that we know the actual text (we could do this easier with - # a RichTextLabel but then we'd need to await idle_frame which is annoying) - var bbcode_positions = find_bbcode_positions_in_string(text) - var accumulaive_length_offset = 0 - for position in bbcode_positions: - # Ignore our own markers - if position.code in ["wait", "speed", "/speed", "do", "do!", "set", "next", "if", "else", "/if"]: - continue - - bbcodes.append({ - bbcode = position.bbcode, - start = position.start, - offset_start = position.start - accumulaive_length_offset - }) - accumulaive_length_offset += position.bbcode.length() - - for bb in bbcodes: - text = text.substr(0, bb.offset_start) + text.substr(bb.offset_start + bb.bbcode.length()) - - # Now find any dialogue markers - var next_bbcode_position = find_bbcode_positions_in_string(text, false) - var limit = 0 - while next_bbcode_position.size() > 0 and limit < 1000: - limit += 1 - - var bbcode = next_bbcode_position[0] - - var index = bbcode.start - var code = bbcode.code - var raw_args = bbcode.raw_args - var args = {} - if code in ["do", "do!", "set"]: - args["value"] = extract_mutation("%s %s" % [code, raw_args]) - else: - # Could be something like: - # "=1.0" - # " rate=20 level=10" - if raw_args and raw_args[0] == "=": - raw_args = "value" + raw_args - for pair in raw_args.strip_edges().split(" "): - if "=" in pair: - var bits = pair.split("=") - args[bits[0]] = bits[1] - - match code: - "wait": - if pauses.has(index): - pauses[index] += args.get("value").to_float() - else: - pauses[index] = args.get("value").to_float() - "speed": - speeds[index] = args.get("value").to_float() - "/speed": - speeds[index] = 1.0 - "do", "do!", "set": - mutations.append([index, args.get("value")]) - "next": - time = args.get("value") if args.has("value") else "0" - - # Find any BB codes that are after this index and remove the length from their start - var length = bbcode.bbcode.length() - for bb in bbcodes: - if bb.offset_start > bbcode.start: - bb.offset_start -= length - bb.start -= length - - # Find any escaped brackets after this that need moving - for i in range(0, escaped_open_brackets.size()): - if escaped_open_brackets[i] > bbcode.start: - escaped_open_brackets[i] -= length - for i in range(0, escaped_close_brackets.size()): - if escaped_close_brackets[i] > bbcode.start: - escaped_close_brackets[i] -= length - - text = text.substr(0, index) + text.substr(index + length) - next_bbcode_position = find_bbcode_positions_in_string(text, false) - - # Put the BB Codes back in - for bb in bbcodes: - text = text.insert(bb.start, bb.bbcode) - - # Put the escaped brackets back in - for index in escaped_open_brackets: - text = text.left(index) + "[" + text.right(text.length() - index - 1) - for index in escaped_close_brackets: - text = text.left(index) + "]" + text.right(text.length() - index - 1) - - return ResolvedLineData.new({ - text = text, - pauses = pauses, - speeds = speeds, - mutations = mutations, - time = time - }) - - -func find_bbcode_positions_in_string(string: String, find_all: bool = true) -> Array[Dictionary]: - if not "[" in string: return [] - - var positions: Array[Dictionary] = [] - - var open_brace_count: int = 0 - var start: int = 0 - var bbcode: String = "" - var code: String = "" - var is_finished_code: bool = false - for i in range(0, string.length()): - if string[i] == "[": - if open_brace_count == 0: - start = i - bbcode = "" - code = "" - is_finished_code = false - open_brace_count += 1 - - else: - if not is_finished_code and (string[i].to_upper() != string[i] or string[i] == "/" or string[i] == "!"): - code += string[i] - else: - is_finished_code = true - - if open_brace_count > 0: - bbcode += string[i] - - if string[i] == "]": - open_brace_count -= 1 - if open_brace_count == 0 and not code in ["if", "else", "/if"]: - positions.append({ - bbcode = bbcode, - code = code, - start = start, - raw_args = bbcode.substr(code.length() + 1, bbcode.length() - code.length() - 2).strip_edges() - }) - - if not find_all: - return positions - - return positions - - -func tokenise(text: String, line_type: String, index: int) -> Array: - var tokens: Array[Dictionary] = [] - var limit: int = 0 - while text.strip_edges() != "" and limit < 1000: - limit += 1 - var found = find_match(text) - if found.size() > 0: - tokens.append({ - index = index, - type = found.type, - value = found.value - }) - index += found.value.length() - text = found.remaining_text - elif text.begins_with(" "): - index += 1 - text = text.substr(1) - else: - return build_token_tree_error(DialogueConstants.ERR_INVALID_EXPRESSION, index) - - return build_token_tree(tokens, line_type, "")[0] - - -func build_token_tree_error(error: int, index: int) -> Array: - return [{ type = DialogueConstants.TOKEN_ERROR, value = error, index = index }] - - -func build_token_tree(tokens: Array[Dictionary], line_type: String, expected_close_token: String) -> Array: - var tree: Array[Dictionary] = [] - var limit = 0 - while tokens.size() > 0 and limit < 1000: - limit += 1 - var token = tokens.pop_front() - - var error = check_next_token(token, tokens, line_type, expected_close_token) - if error != OK: - return [build_token_tree_error(error, token.index), tokens] - - match token.type: - DialogueConstants.TOKEN_FUNCTION: - var sub_tree = build_token_tree(tokens, line_type, DialogueConstants.TOKEN_PARENS_CLOSE) - - if sub_tree[0].size() > 0 and sub_tree[0][0].type == DialogueConstants.TOKEN_ERROR: - return [build_token_tree_error(sub_tree[0][0].value, token.index), tokens] - - tree.append({ - type = DialogueConstants.TOKEN_FUNCTION, - # Consume the trailing "(" - function = token.value.substr(0, token.value.length() - 1), - value = tokens_to_list(sub_tree[0]) - }) - tokens = sub_tree[1] - - DialogueConstants.TOKEN_DICTIONARY_REFERENCE: - var sub_tree = build_token_tree(tokens, line_type, DialogueConstants.TOKEN_BRACKET_CLOSE) - - if sub_tree[0].size() > 0 and sub_tree[0][0].type == DialogueConstants.TOKEN_ERROR: - return [build_token_tree_error(sub_tree[0][0].value, token.index), tokens] - - var args = tokens_to_list(sub_tree[0]) - if args.size() != 1: - return [build_token_tree_error(DialogueConstants.ERR_INVALID_INDEX, token.index), tokens] - - tree.append({ - type = DialogueConstants.TOKEN_DICTIONARY_REFERENCE, - # Consume the trailing "[" - variable = token.value.substr(0, token.value.length() - 1), - value = args[0] - }) - tokens = sub_tree[1] - - DialogueConstants.TOKEN_BRACE_OPEN: - var sub_tree = build_token_tree(tokens, line_type, DialogueConstants.TOKEN_BRACE_CLOSE) - - if sub_tree[0].size() > 0 and sub_tree[0][0].type == DialogueConstants.TOKEN_ERROR: - return [build_token_tree_error(sub_tree[0][0].value, token.index), tokens] - - var t = sub_tree[0] - for i in range(0, t.size() - 2): - # Convert Lua style dictionaries to string keys - if t[i].type == DialogueConstants.TOKEN_VARIABLE and t[i+1].type == DialogueConstants.TOKEN_ASSIGNMENT: - t[i].type = DialogueConstants.TOKEN_STRING - t[i+1].type = DialogueConstants.TOKEN_COLON - t[i+1].erase("value") - - tree.append({ - type = DialogueConstants.TOKEN_DICTIONARY, - value = tokens_to_dictionary(sub_tree[0]) - }) - - tokens = sub_tree[1] - - DialogueConstants.TOKEN_BRACKET_OPEN: - var sub_tree = build_token_tree(tokens, line_type, DialogueConstants.TOKEN_BRACKET_CLOSE) - - if sub_tree[0].size() > 0 and sub_tree[0][0].type == DialogueConstants.TOKEN_ERROR: - return [build_token_tree_error(sub_tree[0][0].value, token.index), tokens] - - var type = DialogueConstants.TOKEN_ARRAY - var value = tokens_to_list(sub_tree[0]) - - # See if this is referencing a nested dictionary value - if tree.size() > 0: - var previous_token = tree[tree.size() - 1] - if previous_token.type in [DialogueConstants.TOKEN_DICTIONARY_REFERENCE, DialogueConstants.TOKEN_DICTIONARY_NESTED_REFERENCE]: - type = DialogueConstants.TOKEN_DICTIONARY_NESTED_REFERENCE - value = value[0] - - tree.append({ - type = type, - value = value - }) - tokens = sub_tree[1] - - DialogueConstants.TOKEN_PARENS_OPEN: - var sub_tree = build_token_tree(tokens, line_type, DialogueConstants.TOKEN_PARENS_CLOSE) - - if sub_tree[0][0].type == DialogueConstants.TOKEN_ERROR: - return [build_token_tree_error(sub_tree[0][0].value, token.index), tokens] - - tree.append({ - type = DialogueConstants.TOKEN_GROUP, - value = sub_tree[0] - }) - tokens = sub_tree[1] - - DialogueConstants.TOKEN_PARENS_CLOSE, \ - DialogueConstants.TOKEN_BRACE_CLOSE, \ - DialogueConstants.TOKEN_BRACKET_CLOSE: - if token.type != expected_close_token: - return [build_token_tree_error(DialogueConstants.ERR_UNEXPECTED_CLOSING_BRACKET, token.index), tokens] - - return [tree, tokens] - - DialogueConstants.TOKEN_NOT: - # Double nots negate each other - if tokens.size() > 0 and tokens.front().type == DialogueConstants.TOKEN_NOT: - tokens.pop_front() - else: - tree.append({ - type = token.type - }) - - DialogueConstants.TOKEN_COMMA, \ - DialogueConstants.TOKEN_COLON, \ - DialogueConstants.TOKEN_DOT: - tree.append({ - type = token.type - }) - - DialogueConstants.TOKEN_COMPARISON, \ - DialogueConstants.TOKEN_ASSIGNMENT, \ - DialogueConstants.TOKEN_OPERATOR, \ - DialogueConstants.TOKEN_AND_OR, \ - DialogueConstants.TOKEN_VARIABLE: - var value = token.value.strip_edges() - if value == "&&": - value = "and" - elif value == "||": - value = "or" - tree.append({ - type = token.type, - value = value - }) - - DialogueConstants.TOKEN_STRING: - if token.value.begins_with("&"): - tree.append({ - type = token.type, - value = StringName(token.value.substr(2, token.value.length() - 3)) - }) - else: - tree.append({ - type = token.type, - value = token.value.substr(1, token.value.length() - 2) - }) - - DialogueConstants.TOKEN_CONDITION: - return [build_token_tree_error(DialogueConstants.ERR_UNEXPECTED_CONDITION, token.index), token] - - DialogueConstants.TOKEN_BOOL: - tree.append({ - type = token.type, - value = token.value.to_lower() == "true" - }) - - DialogueConstants.TOKEN_NUMBER: - var value = token.value.to_float() if "." in token.value else token.value.to_int() - # If previous token is a number and this one is a negative number then - # inject a minus operator token in between them. - if tree.size() > 0 and token.value.begins_with("-") and tree[tree.size() - 1].type == DialogueConstants.TOKEN_NUMBER: - tree.append(({ - type = DialogueConstants.TOKEN_OPERATOR, - value = "-" - })) - tree.append({ - type = token.type, - value = -1 * value - }) - else: - tree.append({ - type = token.type, - value = value - }) - - if expected_close_token != "": - var index: int = tokens[0].index if tokens.size() > 0 else 0 - return [build_token_tree_error(DialogueConstants.ERR_MISSING_CLOSING_BRACKET, index), tokens] - - return [tree, tokens] - - -func check_next_token(token: Dictionary, next_tokens: Array[Dictionary], line_type: String, expected_close_token: String) -> Error: - var next_token: Dictionary = { type = null } - if next_tokens.size() > 0: - next_token = next_tokens.front() - - # Guard for assigning in a condition. If the assignment token isn't inside a Lua dictionary - # then it's an unexpected assignment in a condition line. - if token.type == DialogueConstants.TOKEN_ASSIGNMENT and line_type == DialogueConstants.TYPE_CONDITION and not next_tokens.any(func(t): return t.type == expected_close_token): - return DialogueConstants.ERR_UNEXPECTED_ASSIGNMENT - - # Special case for a negative number after this one - if token.type == DialogueConstants.TOKEN_NUMBER and next_token.type == DialogueConstants.TOKEN_NUMBER and next_token.value.begins_with("-"): - return OK - - var expected_token_types = [] - var unexpected_token_types = [] - match token.type: - DialogueConstants.TOKEN_FUNCTION, \ - DialogueConstants.TOKEN_PARENS_OPEN: - unexpected_token_types = [ - null, - DialogueConstants.TOKEN_COMMA, - DialogueConstants.TOKEN_COLON, - DialogueConstants.TOKEN_COMPARISON, - DialogueConstants.TOKEN_ASSIGNMENT, - DialogueConstants.TOKEN_OPERATOR, - DialogueConstants.TOKEN_AND_OR, - DialogueConstants.TOKEN_DOT - ] - - DialogueConstants.TOKEN_BRACKET_CLOSE: - unexpected_token_types = [ - DialogueConstants.TOKEN_NOT, - DialogueConstants.TOKEN_BOOL, - DialogueConstants.TOKEN_STRING, - DialogueConstants.TOKEN_NUMBER, - DialogueConstants.TOKEN_VARIABLE - ] - - DialogueConstants.TOKEN_BRACE_OPEN: - expected_token_types = [ - DialogueConstants.TOKEN_STRING, - DialogueConstants.TOKEN_VARIABLE, - DialogueConstants.TOKEN_NUMBER, - DialogueConstants.TOKEN_BRACE_CLOSE - ] - - DialogueConstants.TOKEN_PARENS_CLOSE, \ - DialogueConstants.TOKEN_BRACE_CLOSE: - unexpected_token_types = [ - DialogueConstants.TOKEN_NOT, - DialogueConstants.TOKEN_ASSIGNMENT, - DialogueConstants.TOKEN_BOOL, - DialogueConstants.TOKEN_STRING, - DialogueConstants.TOKEN_NUMBER, - DialogueConstants.TOKEN_VARIABLE - ] - - DialogueConstants.TOKEN_COMPARISON, \ - DialogueConstants.TOKEN_OPERATOR, \ - DialogueConstants.TOKEN_COMMA, \ - DialogueConstants.TOKEN_DOT, \ - DialogueConstants.TOKEN_NOT, \ - DialogueConstants.TOKEN_AND_OR, \ - DialogueConstants.TOKEN_DICTIONARY_REFERENCE: - unexpected_token_types = [ - null, - DialogueConstants.TOKEN_COMMA, - DialogueConstants.TOKEN_COLON, - DialogueConstants.TOKEN_COMPARISON, - DialogueConstants.TOKEN_ASSIGNMENT, - DialogueConstants.TOKEN_OPERATOR, - DialogueConstants.TOKEN_AND_OR, - DialogueConstants.TOKEN_PARENS_CLOSE, - DialogueConstants.TOKEN_BRACE_CLOSE, - DialogueConstants.TOKEN_BRACKET_CLOSE, - DialogueConstants.TOKEN_DOT - ] - - DialogueConstants.TOKEN_COLON: - unexpected_token_types = [ - DialogueConstants.TOKEN_COMMA, - DialogueConstants.TOKEN_COLON, - DialogueConstants.TOKEN_COMPARISON, - DialogueConstants.TOKEN_ASSIGNMENT, - DialogueConstants.TOKEN_OPERATOR, - DialogueConstants.TOKEN_AND_OR, - DialogueConstants.TOKEN_PARENS_CLOSE, - DialogueConstants.TOKEN_BRACE_CLOSE, - DialogueConstants.TOKEN_BRACKET_CLOSE, - DialogueConstants.TOKEN_DOT - ] - - DialogueConstants.TOKEN_BOOL, \ - DialogueConstants.TOKEN_STRING, \ - DialogueConstants.TOKEN_NUMBER: - unexpected_token_types = [ - DialogueConstants.TOKEN_NOT, - DialogueConstants.TOKEN_ASSIGNMENT, - DialogueConstants.TOKEN_BOOL, - DialogueConstants.TOKEN_STRING, - DialogueConstants.TOKEN_NUMBER, - DialogueConstants.TOKEN_VARIABLE, - DialogueConstants.TOKEN_FUNCTION, - DialogueConstants.TOKEN_PARENS_OPEN, - DialogueConstants.TOKEN_BRACE_OPEN, - DialogueConstants.TOKEN_BRACKET_OPEN - ] - - DialogueConstants.TOKEN_VARIABLE: - unexpected_token_types = [ - DialogueConstants.TOKEN_NOT, - DialogueConstants.TOKEN_BOOL, - DialogueConstants.TOKEN_STRING, - DialogueConstants.TOKEN_NUMBER, - DialogueConstants.TOKEN_VARIABLE, - DialogueConstants.TOKEN_FUNCTION, - DialogueConstants.TOKEN_PARENS_OPEN, - DialogueConstants.TOKEN_BRACE_OPEN, - DialogueConstants.TOKEN_BRACKET_OPEN - ] - - if (expected_token_types.size() > 0 and not next_token.type in expected_token_types or unexpected_token_types.size() > 0 and next_token.type in unexpected_token_types): - match next_token.type: - null: - return DialogueConstants.ERR_UNEXPECTED_END_OF_EXPRESSION - - DialogueConstants.TOKEN_FUNCTION: - return DialogueConstants.ERR_UNEXPECTED_FUNCTION - - DialogueConstants.TOKEN_PARENS_OPEN, \ - DialogueConstants.TOKEN_PARENS_CLOSE: - return DialogueConstants.ERR_UNEXPECTED_BRACKET - - DialogueConstants.TOKEN_COMPARISON, \ - DialogueConstants.TOKEN_ASSIGNMENT, \ - DialogueConstants.TOKEN_OPERATOR, \ - DialogueConstants.TOKEN_NOT, \ - DialogueConstants.TOKEN_AND_OR: - return DialogueConstants.ERR_UNEXPECTED_OPERATOR - - DialogueConstants.TOKEN_COMMA: - return DialogueConstants.ERR_UNEXPECTED_COMMA - DialogueConstants.TOKEN_COLON: - return DialogueConstants.ERR_UNEXPECTED_COLON - DialogueConstants.TOKEN_DOT: - return DialogueConstants.ERR_UNEXPECTED_DOT - - DialogueConstants.TOKEN_BOOL: - return DialogueConstants.ERR_UNEXPECTED_BOOLEAN - DialogueConstants.TOKEN_STRING: - return DialogueConstants.ERR_UNEXPECTED_STRING - DialogueConstants.TOKEN_NUMBER: - return DialogueConstants.ERR_UNEXPECTED_NUMBER - DialogueConstants.TOKEN_VARIABLE: - return DialogueConstants.ERR_UNEXPECTED_VARIABLE - - return DialogueConstants.ERR_INVALID_EXPRESSION - - return OK - - -func tokens_to_list(tokens: Array[Dictionary]) -> Array[Array]: - var list: Array[Array] = [] - var current_item: Array[Dictionary] = [] - for token in tokens: - if token.type == DialogueConstants.TOKEN_COMMA: - list.append(current_item) - current_item = [] - else: - current_item.append(token) - - if current_item.size() > 0: - list.append(current_item) - - return list - - -func tokens_to_dictionary(tokens: Array[Dictionary]) -> Dictionary: - var dictionary = {} - for i in range(0, tokens.size()): - if tokens[i].type == DialogueConstants.TOKEN_COLON: - if tokens.size() == i + 2: - dictionary[tokens[i-1]] = tokens[i+1] - else: - dictionary[tokens[i-1]] = { type = DialogueConstants.TOKEN_GROUP, value = tokens.slice(i+1) } - - return dictionary - - -func find_match(input: String) -> Dictionary: - for key in TOKEN_DEFINITIONS.keys(): - var regex = TOKEN_DEFINITIONS.get(key) - var found = regex.search(input) - if found: - return { - type = key, - remaining_text = input.substr(found.strings[0].length()), - value = found.strings[0] - } - - return {} diff --git a/addons/dialogue_manager/components/resolved_line_data.gd b/addons/dialogue_manager/components/resolved_line_data.gd deleted file mode 100644 index 10735862..00000000 --- a/addons/dialogue_manager/components/resolved_line_data.gd +++ /dev/null @@ -1,15 +0,0 @@ -extends RefCounted - -var text: String = "" -var pauses: Dictionary = {} -var speeds: Dictionary = {} -var mutations: Array[Array] = [] -var time: String = "" - - -func _init(data: Dictionary) -> void: - text = data.text - pauses = data.pauses - speeds = data.speeds - mutations = data.mutations - time = data.time diff --git a/addons/dialogue_manager/components/resolved_tag_data.gd b/addons/dialogue_manager/components/resolved_tag_data.gd deleted file mode 100644 index 728cc423..00000000 --- a/addons/dialogue_manager/components/resolved_tag_data.gd +++ /dev/null @@ -1,10 +0,0 @@ -extends RefCounted - - -var tags: PackedStringArray = [] -var line_without_tags: String = "" - - -func _init(data: Dictionary) -> void: - tags = data.tags - line_without_tags = data.line_without_tags diff --git a/addons/dialogue_manager/components/search_and_replace.gd.uid b/addons/dialogue_manager/components/search_and_replace.gd.uid new file mode 100644 index 00000000..66ec8267 --- /dev/null +++ b/addons/dialogue_manager/components/search_and_replace.gd.uid @@ -0,0 +1 @@ +uid://cijsmjkq21cdq diff --git a/addons/dialogue_manager/components/search_and_replace.tscn b/addons/dialogue_manager/components/search_and_replace.tscn index 82dd60d7..52721c4a 100644 --- a/addons/dialogue_manager/components/search_and_replace.tscn +++ b/addons/dialogue_manager/components/search_and_replace.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=2 format=3 uid="uid://gr8nakpbrhby"] -[ext_resource type="Script" path="res://addons/dialogue_manager/components/search_and_replace.gd" id="1_8oj1f"] +[ext_resource type="Script" uid="uid://cijsmjkq21cdq" path="res://addons/dialogue_manager/components/search_and_replace.gd" id="1_8oj1f"] [node name="SearchAndReplace" type="VBoxContainer"] visible = false diff --git a/addons/dialogue_manager/components/title_list.gd.uid b/addons/dialogue_manager/components/title_list.gd.uid new file mode 100644 index 00000000..325ca61d --- /dev/null +++ b/addons/dialogue_manager/components/title_list.gd.uid @@ -0,0 +1 @@ +uid://d0k2wndjj0ifm diff --git a/addons/dialogue_manager/components/title_list.tscn b/addons/dialogue_manager/components/title_list.tscn index 6273122e..ac2b9833 100644 --- a/addons/dialogue_manager/components/title_list.tscn +++ b/addons/dialogue_manager/components/title_list.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=2 format=3 uid="uid://ctns6ouwwd68i"] -[ext_resource type="Script" path="res://addons/dialogue_manager/components/title_list.gd" id="1_5qqmd"] +[ext_resource type="Script" uid="uid://d0k2wndjj0ifm" path="res://addons/dialogue_manager/components/title_list.gd" id="1_5qqmd"] [node name="TitleList" type="VBoxContainer"] anchors_preset = 15 diff --git a/addons/dialogue_manager/components/update_button.gd b/addons/dialogue_manager/components/update_button.gd index 2f77c63d..cf3f1b94 100644 --- a/addons/dialogue_manager/components/update_button.gd +++ b/addons/dialogue_manager/components/update_button.gd @@ -86,9 +86,9 @@ func _on_update_button_pressed() -> void: if needs_reload: var will_refresh = on_before_refresh.call() if will_refresh: - Engine.get_meta("DialogueManagerPlugin").get_editor_interface().restart_editor(true) + EditorInterface.restart_editor(true) else: - var scale: float = Engine.get_meta("DialogueManagerPlugin").get_editor_interface().get_editor_scale() + var scale: float = EditorInterface.get_editor_scale() download_dialog.min_size = Vector2(300, 250) * scale download_dialog.popup_centered() @@ -117,7 +117,7 @@ func _on_download_update_panel_failed() -> void: func _on_needs_reload_dialog_confirmed() -> void: - Engine.get_meta("DialogueManagerPlugin").get_editor_interface().restart_editor(true) + EditorInterface.restart_editor(true) func _on_timer_timeout() -> void: diff --git a/addons/dialogue_manager/components/update_button.gd.uid b/addons/dialogue_manager/components/update_button.gd.uid new file mode 100644 index 00000000..9981132e --- /dev/null +++ b/addons/dialogue_manager/components/update_button.gd.uid @@ -0,0 +1 @@ +uid://cr1tt12dh5ecr diff --git a/addons/dialogue_manager/components/update_button.tscn b/addons/dialogue_manager/components/update_button.tscn index 533a94e1..6cff3471 100644 --- a/addons/dialogue_manager/components/update_button.tscn +++ b/addons/dialogue_manager/components/update_button.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=3 format=3 uid="uid://co8yl23idiwbi"] -[ext_resource type="Script" path="res://addons/dialogue_manager/components/update_button.gd" id="1_d2tpb"] +[ext_resource type="Script" uid="uid://cr1tt12dh5ecr" path="res://addons/dialogue_manager/components/update_button.gd" id="1_d2tpb"] [ext_resource type="PackedScene" uid="uid://qdxrxv3c3hxk" path="res://addons/dialogue_manager/components/download_update_panel.tscn" id="2_iwm7r"] [node name="UpdateButton" type="Button"] diff --git a/addons/dialogue_manager/constants.gd b/addons/dialogue_manager/constants.gd index 5913aa1a..ea2844e7 100644 --- a/addons/dialogue_manager/constants.gd +++ b/addons/dialogue_manager/constants.gd @@ -1,9 +1,23 @@ -extends Node +class_name DMConstants extends RefCounted const USER_CONFIG_PATH = "user://dialogue_manager_user_config.json" const CACHE_PATH = "user://dialogue_manager_cache.json" + +enum MutationBehaviour { + Wait, + DoNotWait, + Skip +} + +enum TranslationSource { + None, + Guess, + CSV, + PO +} + # Token types const TOKEN_FUNCTION = &"function" @@ -33,21 +47,26 @@ const TOKEN_NUMBER = &"number" const TOKEN_VARIABLE = &"variable" const TOKEN_COMMENT = &"comment" +const TOKEN_VALUE = &"value" const TOKEN_ERROR = &"error" # Line types -const TYPE_UNKNOWN = &"unknown" +const TYPE_UNKNOWN = &"" +const TYPE_IMPORT = &"import" +const TYPE_COMMENT = &"comment" const TYPE_RESPONSE = &"response" const TYPE_TITLE = &"title" const TYPE_CONDITION = &"condition" +const TYPE_WHILE = &"while" +const TYPE_MATCH = &"match" +const TYPE_WHEN = &"when" const TYPE_MUTATION = &"mutation" const TYPE_GOTO = &"goto" const TYPE_DIALOGUE = &"dialogue" +const TYPE_RANDOM = &"random" const TYPE_ERROR = &"error" -const TYPE_ELSE = &"else" - # Line IDs const ID_NULL = &"" @@ -94,6 +113,11 @@ const ERR_UNEXPECTED_VARIABLE = 132 const ERR_INVALID_INDEX = 133 const ERR_UNEXPECTED_ASSIGNMENT = 134 const ERR_UNKNOWN_USING = 135 +const ERR_EXPECTED_WHEN_OR_ELSE = 136 +const ERR_ONLY_ONE_ELSE_ALLOWED = 137 +const ERR_WHEN_MUST_BELONG_TO_MATCH = 138 +const ERR_CONCURRENT_LINE_WITHOUT_ORIGIN = 139 +const ERR_GOTO_NOT_ALLOWED_ON_CONCURRECT_LINES = 140 ## Get the error message @@ -169,14 +193,22 @@ static func get_error_message(error: int) -> String: return translate(&"errors.unexpected_assignment") ERR_UNKNOWN_USING: return translate(&"errors.unknown_using") + ERR_EXPECTED_WHEN_OR_ELSE: + return translate(&"errors.expected_when_or_else") + ERR_ONLY_ONE_ELSE_ALLOWED: + return translate(&"errors.only_one_else_allowed") + ERR_WHEN_MUST_BELONG_TO_MATCH: + return translate(&"errors.when_must_belong_to_match") + ERR_CONCURRENT_LINE_WITHOUT_ORIGIN: + return translate(&"errors.concurrent_line_without_origin") + ERR_GOTO_NOT_ALLOWED_ON_CONCURRECT_LINES: + return translate(&"errors.goto_not_allowed_on_concurrect_lines") return translate(&"errors.unknown") static func translate(string: String) -> String: - var temp_node = new() - var base_path = temp_node.get_script().resource_path.get_base_dir() - temp_node.free() + var base_path = new().get_script().resource_path.get_base_dir() var language: String = TranslationServer.get_tool_locale() var translations_path: String = "%s/l10n/%s.po" % [base_path, language] diff --git a/addons/dialogue_manager/constants.gd.uid b/addons/dialogue_manager/constants.gd.uid new file mode 100644 index 00000000..f431917b --- /dev/null +++ b/addons/dialogue_manager/constants.gd.uid @@ -0,0 +1 @@ +uid://b1oarbmjtyesf diff --git a/addons/dialogue_manager/dialogue_label.gd b/addons/dialogue_manager/dialogue_label.gd index 3a6caeb9..da07b45b 100644 --- a/addons/dialogue_manager/dialogue_label.gd +++ b/addons/dialogue_manager/dialogue_label.gd @@ -187,12 +187,13 @@ func _mutate_inline_mutations(index: int) -> void: if inline_mutation[0] > index: return if inline_mutation[0] == index and not _already_mutated_indices.has(index): - _already_mutated_indices.append(index) _is_awaiting_mutation = true # The DialogueManager can't be referenced directly here so we need to get it by its path - await Engine.get_singleton("DialogueManager").mutate(inline_mutation[1], dialogue_line.extra_game_states, true) + await Engine.get_singleton("DialogueManager")._mutate(inline_mutation[1], dialogue_line.extra_game_states, true) _is_awaiting_mutation = false + _already_mutated_indices.append(index) + # Determine if the current autopause character at the cursor should qualify to pause typing. func _should_auto_pause() -> bool: diff --git a/addons/dialogue_manager/dialogue_label.gd.uid b/addons/dialogue_manager/dialogue_label.gd.uid new file mode 100644 index 00000000..6bf86b15 --- /dev/null +++ b/addons/dialogue_manager/dialogue_label.gd.uid @@ -0,0 +1 @@ +uid://g32um0mltv5d diff --git a/addons/dialogue_manager/dialogue_label.tscn b/addons/dialogue_manager/dialogue_label.tscn index df48b649..00959334 100644 --- a/addons/dialogue_manager/dialogue_label.tscn +++ b/addons/dialogue_manager/dialogue_label.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=2 format=3 uid="uid://ckvgyvclnwggo"] -[ext_resource type="Script" path="res://addons/dialogue_manager/dialogue_label.gd" id="1_cital"] +[ext_resource type="Script" uid="uid://g32um0mltv5d" path="res://addons/dialogue_manager/dialogue_label.gd" id="1_cital"] [node name="DialogueLabel" type="RichTextLabel"] anchors_preset = 10 diff --git a/addons/dialogue_manager/dialogue_line.gd b/addons/dialogue_manager/dialogue_line.gd old mode 100644 new mode 100755 index ced12f21..7213854c --- a/addons/dialogue_manager/dialogue_line.gd +++ b/addons/dialogue_manager/dialogue_line.gd @@ -2,14 +2,11 @@ class_name DialogueLine extends RefCounted -const _DialogueConstants = preload("./constants.gd") - - ## The ID of this line var id: String ## The internal type of this dialogue object. One of [code]TYPE_DIALOGUE[/code] or [code]TYPE_MUTATION[/code] -var type: String = _DialogueConstants.TYPE_DIALOGUE +var type: String = DMConstants.TYPE_DIALOGUE ## The next line ID after this line. var next_id: String = "" @@ -41,6 +38,9 @@ var inline_mutations: Array[Array] = [] ## A list of responses attached to this line of dialogue. var responses: Array = [] +## A list of lines that are spoken simultaneously with this one. +var concurrent_lines: Array[DialogueLine] = [] + ## A list of any extra game states to check when resolving variables and mutations. var extra_game_states: Array = [] @@ -65,7 +65,7 @@ func _init(data: Dictionary = {}) -> void: extra_game_states = data.get("extra_game_states", []) match type: - _DialogueConstants.TYPE_DIALOGUE: + DMConstants.TYPE_DIALOGUE: character = data.character character_replacements = data.get("character_replacements", [] as Array[Dictionary]) text = data.text @@ -76,16 +76,17 @@ func _init(data: Dictionary = {}) -> void: inline_mutations = data.get("inline_mutations", [] as Array[Array]) time = data.get("time", "") tags = data.get("tags", []) + concurrent_lines = data.get("concurrent_lines", [] as Array[DialogueLine]) - _DialogueConstants.TYPE_MUTATION: + DMConstants.TYPE_MUTATION: mutation = data.mutation func _to_string() -> String: match type: - _DialogueConstants.TYPE_DIALOGUE: + DMConstants.TYPE_DIALOGUE: return "<DialogueLine character=\"%s\" text=\"%s\">" % [character, text] - _DialogueConstants.TYPE_MUTATION: + DMConstants.TYPE_MUTATION: return "<DialogueLine mutation>" return "" diff --git a/addons/dialogue_manager/dialogue_line.gd.uid b/addons/dialogue_manager/dialogue_line.gd.uid new file mode 100644 index 00000000..7ec7029a --- /dev/null +++ b/addons/dialogue_manager/dialogue_line.gd.uid @@ -0,0 +1 @@ +uid://rhuq0eyf8ar2 diff --git a/addons/dialogue_manager/dialogue_manager.gd b/addons/dialogue_manager/dialogue_manager.gd index 13cd3b63..ab9a17ed 100644 --- a/addons/dialogue_manager/dialogue_manager.gd +++ b/addons/dialogue_manager/dialogue_manager.gd @@ -1,15 +1,15 @@ extends Node - -const DialogueConstants = preload("./constants.gd") -const Builtins = preload("./utilities/builtins.gd") -const DialogueSettings = preload("./settings.gd") const DialogueResource = preload("./dialogue_resource.gd") const DialogueLine = preload("./dialogue_line.gd") const DialogueResponse = preload("./dialogue_response.gd") -const DialogueManagerParser = preload("./components/parser.gd") -const DialogueManagerParseResult = preload("./components/parse_result.gd") -const ResolvedLineData = preload("./components/resolved_line_data.gd") + +const DMConstants = preload("./constants.gd") +const Builtins = preload("./utilities/builtins.gd") +const DMSettings = preload("./settings.gd") +const DMCompiler = preload("./compiler/compiler.gd") +const DMCompilerResult = preload("./compiler/compiler_result.gd") +const DMResolvedLineData = preload("./compiler/resolved_line_data.gd") ## Emitted when a dialogue balloon is created and dialogue starts @@ -31,24 +31,13 @@ signal dialogue_ended(resource: DialogueResource) ## Used internally. signal bridge_get_next_dialogue_line_completed(line: DialogueLine) +## Used internally +signal bridge_dialogue_started(resource: DialogueResource) + ## Used inernally signal bridge_mutated() -enum MutationBehaviour { - Wait, - DoNotWait, - Skip -} - -enum TranslationSource { - None, - Guess, - CSV, - PO -} - - ## The list of globals that dialogue can query var game_states: Array = [] @@ -59,7 +48,7 @@ var include_singletons: bool = true var include_classes: bool = true ## Manage translation behaviour -var translation_source: TranslationSource = TranslationSource.Guess +var translation_source: DMConstants.TranslationSource = DMConstants.TranslationSource.Guess ## Used to resolve the current scene. Override if your game manages the current scene itself. var get_current_scene: Callable = func(): @@ -74,6 +63,8 @@ var _autoloads: Dictionary = {} var _node_properties: Array = [] var _method_info_cache: Dictionary = {} +var _dotnet_dialogue_manager: RefCounted + func _ready() -> void: # Cache the known Node2D properties @@ -84,52 +75,50 @@ func _ready() -> void: temp_node.free() # Make the dialogue manager available as a singleton - if Engine.has_singleton("DialogueManager"): - Engine.unregister_singleton("DialogueManager") - Engine.register_singleton("DialogueManager", self) - - # Connect up the C# signals if need be - if DialogueSettings.check_for_dotnet_solution(): - _get_dotnet_dialogue_manager().Prepare() + if not Engine.has_singleton("DialogueManager"): + Engine.register_singleton("DialogueManager", self) ## Step through lines and run any mutations until we either hit some dialogue or the end of the conversation -func get_next_dialogue_line(resource: DialogueResource, key: String = "", extra_game_states: Array = [], mutation_behaviour: MutationBehaviour = MutationBehaviour.Wait) -> DialogueLine: +func get_next_dialogue_line(resource: DialogueResource, key: String = "", extra_game_states: Array = [], mutation_behaviour: DMConstants.MutationBehaviour = DMConstants.MutationBehaviour.Wait) -> DialogueLine: # You have to provide a valid dialogue resource if resource == null: - assert(false, DialogueConstants.translate(&"runtime.no_resource")) + assert(false, DMConstants.translate(&"runtime.no_resource")) if resource.lines.size() == 0: - assert(false, DialogueConstants.translate(&"runtime.no_content").format({ file_path = resource.resource_path })) + assert(false, DMConstants.translate(&"runtime.no_content").format({ file_path = resource.resource_path })) # Inject any "using" states into the game_states for state_name in resource.using_states: var autoload = Engine.get_main_loop().root.get_node_or_null(state_name) if autoload == null: - printerr(DialogueConstants.translate(&"runtime.unknown_autoload").format({ autoload = state_name })) + printerr(DMConstants.translate(&"runtime.unknown_autoload").format({ autoload = state_name })) else: extra_game_states = [autoload] + extra_game_states + # Inject "self" into the extra game states. + extra_game_states = [{ "self": resource }] + extra_game_states + # Get the line data var dialogue: DialogueLine = await get_line(resource, key, extra_game_states) # If our dialogue is nothing then we hit the end - if not is_valid(dialogue): - (func(): dialogue_ended.emit(resource)).call_deferred() + if not _is_valid(dialogue): + dialogue_ended.emit.call_deferred(resource) return null # Run the mutation if it is one - if dialogue.type == DialogueConstants.TYPE_MUTATION: - var actual_next_id: String = dialogue.next_id.split(",")[0] + if dialogue.type == DMConstants.TYPE_MUTATION: + var actual_next_id: String = dialogue.next_id.split("|")[0] match mutation_behaviour: - MutationBehaviour.Wait: - await mutate(dialogue.mutation, extra_game_states) - MutationBehaviour.DoNotWait: - mutate(dialogue.mutation, extra_game_states) - MutationBehaviour.Skip: + DMConstants.MutationBehaviour.Wait: + await _mutate(dialogue.mutation, extra_game_states) + DMConstants.MutationBehaviour.DoNotWait: + _mutate(dialogue.mutation, extra_game_states) + DMConstants.MutationBehaviour.Skip: pass - if actual_next_id in [DialogueConstants.ID_END_CONVERSATION, DialogueConstants.ID_NULL, null]: + if actual_next_id in [DMConstants.ID_END_CONVERSATION, DMConstants.ID_NULL, null]: # End the conversation - (func(): dialogue_ended.emit(resource)).call_deferred() + dialogue_ended.emit.call_deferred(resource) return null else: return await get_next_dialogue_line(resource, dialogue.next_id, extra_game_states, mutation_behaviour) @@ -138,12 +127,174 @@ func get_next_dialogue_line(resource: DialogueResource, key: String = "", extra_ return dialogue -func get_resolved_line_data(data: Dictionary, extra_game_states: Array = []) -> ResolvedLineData: +## Get a line by its ID +func get_line(resource: DialogueResource, key: String, extra_game_states: Array) -> DialogueLine: + key = key.strip_edges() + + # See if we were given a stack instead of just the one key + var stack: Array = key.split("|") + key = stack.pop_front() + var id_trail: String = "" if stack.size() == 0 else "|" + "|".join(stack) + + # Key is blank so just use the first title (or start of file) + if key == null or key == "": + if resource.first_title.is_empty(): + key = resource.lines.keys()[0] + else: + key = resource.first_title + + # See if we just ended the conversation + if key in [DMConstants.ID_END, DMConstants.ID_NULL, null]: + if stack.size() > 0: + return await get_line(resource, "|".join(stack), extra_game_states) + else: + return null + elif key == DMConstants.ID_END_CONVERSATION: + return null + + # See if it is a title + if key.begins_with("~ "): + key = key.substr(2) + if resource.titles.has(key): + key = resource.titles.get(key) + + if key in resource.titles.values(): + passed_title.emit(resource.titles.find_key(key)) + + if not resource.lines.has(key): + assert(false, DMConstants.translate(&"errors.key_not_found").format({ key = key })) + + var data: Dictionary = resource.lines.get(key) + + # If next_id is an expression we need to resolve it. + if data.has(&"next_id_expression"): + data.next_id = await _resolve(data.next_id_expression, extra_game_states) + + # This title key points to another title key so we should jump there instead + if data.type == DMConstants.TYPE_TITLE and data.next_id in resource.titles.values(): + return await get_line(resource, data.next_id + id_trail, extra_game_states) + + # Handle match statements + if data.type == DMConstants.TYPE_MATCH: + var value = await _resolve_condition_value(data, extra_game_states) + var else_cases: Array[Dictionary] = data.cases.filter(func(s): return s.has("is_else")) + var else_case: Dictionary = {} if else_cases.size() == 0 else else_cases.front() + var next_id: String = "" + for case in data.cases: + if case == else_case: + continue + elif await _check_case_value(value, case, extra_game_states): + next_id = case.next_id + # Nothing matched so check for else case + if next_id == "": + if not else_case.is_empty(): + next_id = else_case.next_id + else: + next_id = data.next_id_after + return await get_line(resource, next_id + id_trail, extra_game_states) + + # Check for weighted random lines. + if data.has(&"siblings"): + # Only count siblings that pass their condition (if they have one). + var successful_siblings: Array = data.siblings.filter(func(sibling): return not sibling.has("condition") or await _check_condition(sibling, extra_game_states)) + var target_weight: float = randf_range(0, successful_siblings.reduce(func(total, sibling): return total + sibling.weight, 0)) + var cummulative_weight: float = 0 + for sibling in successful_siblings: + if target_weight < cummulative_weight + sibling.weight: + data = resource.lines.get(sibling.id) + break + else: + cummulative_weight += sibling.weight + + # Find any simultaneously said lines. + var concurrent_lines: Array[DialogueLine] = [] + if data.has(&"concurrent_lines"): + # If the list includes this line then it isn't the origin line so ignore it. + if not data.concurrent_lines.has(data.id): + for concurrent_id: String in data.concurrent_lines: + var concurrent_line: DialogueLine = await get_line(resource, concurrent_id, extra_game_states) + if concurrent_line: + concurrent_lines.append(concurrent_line) + + # If this line is blank and it's the last line then check for returning snippets. + if data.type in [DMConstants.TYPE_COMMENT, DMConstants.TYPE_UNKNOWN]: + if data.next_id in [DMConstants.ID_END, DMConstants.ID_NULL, null]: + if stack.size() > 0: + return await get_line(resource, "|".join(stack), extra_game_states) + else: + return null + else: + return await get_line(resource, data.next_id + id_trail, extra_game_states) + + # If the line is a random block then go to the start of the block. + elif data.type == DMConstants.TYPE_RANDOM: + data = resource.lines.get(data.next_id) + + # Check conditions. + elif data.type in [DMConstants.TYPE_CONDITION, DMConstants.TYPE_WHILE]: + # "else" will have no actual condition. + if await _check_condition(data, extra_game_states): + return await get_line(resource, data.next_id + id_trail, extra_game_states) + elif data.has("next_sibling_id") and not data.next_sibling_id.is_empty(): + return await get_line(resource, data.next_sibling_id + id_trail, extra_game_states) + else: + return await get_line(resource, data.next_id_after + id_trail, extra_game_states) + + # Evaluate jumps. + elif data.type == DMConstants.TYPE_GOTO: + if data.is_snippet and not id_trail.begins_with("|" + data.next_id_after): + id_trail = "|" + data.next_id_after + id_trail + return await get_line(resource, data.next_id + id_trail, extra_game_states) + + elif data.type == DMConstants.TYPE_DIALOGUE: + if not data.has(&"id"): + data.id = key + + # Set up a line object. + var line: DialogueLine = await create_dialogue_line(data, extra_game_states) + line.concurrent_lines = concurrent_lines + + # If the jump point somehow has no content then just end. + if not line: return null + + # If we are the first of a list of responses then get the other ones. + if data.type == DMConstants.TYPE_RESPONSE: + # Note: For some reason C# has occasional issues with using the responses property directly + # so instead we use set and get here. + line.set(&"responses", await _get_responses(data.get(&"responses", []), resource, id_trail, extra_game_states)) + return line + + # Inject the next node's responses if they have any. + if resource.lines.has(line.next_id): + var next_line: Dictionary = resource.lines.get(line.next_id) + + # If the response line is marked as a title then make sure to emit the passed_title signal. + if line.next_id in resource.titles.values(): + passed_title.emit(resource.titles.find_key(line.next_id)) + + # If the responses come from a snippet then we need to come back here afterwards. + if next_line.type == DMConstants.TYPE_GOTO and next_line.is_snippet and not id_trail.begins_with("|" + next_line.next_id_after): + id_trail = "|" + next_line.next_id_after + id_trail + + # If the next line is a title then check where it points to see if that is a set of responses. + while [DMConstants.TYPE_TITLE, DMConstants.TYPE_GOTO].has(next_line.type) and resource.lines.has(next_line.next_id): + next_line = resource.lines.get(next_line.next_id) + + if next_line != null and next_line.type == DMConstants.TYPE_RESPONSE: + # Note: For some reason C# has occasional issues with using the responses property directly + # so instead we use set and get here. + line.set(&"responses", await _get_responses(next_line.get(&"responses", []), resource, id_trail, extra_game_states)) + + line.next_id = "|".join(stack) if line.next_id == DMConstants.ID_NULL else line.next_id + id_trail + return line + +## Replace any variables, etc in the text. +func get_resolved_line_data(data: Dictionary, extra_game_states: Array = []) -> DMResolvedLineData: var text: String = translate(data) # Resolve variables - for replacement in data.text_replacements: - var value = await resolve(replacement.expression.duplicate(true), extra_game_states) + for replacement in data.get(&"text_replacements", [] as Array[Dictionary]): + var value = await _resolve(replacement.expression.duplicate(true), extra_game_states) var index: int = text.find(replacement.value_in_text) if index == -1: # The replacement wasn't found but maybe the regular quotes have been replaced @@ -152,20 +303,20 @@ func get_resolved_line_data(data: Dictionary, extra_game_states: Array = []) -> if index > -1: text = text.substr(0, index) + str(value) + text.substr(index + replacement.value_in_text.length()) - var parser: DialogueManagerParser = DialogueManagerParser.new() + var compilation: DMCompilation = DMCompilation.new() # Resolve random groups - for found in parser.INLINE_RANDOM_REGEX.search_all(text): + for found in compilation.regex.INLINE_RANDOM_REGEX.search_all(text): var options = found.get_string(&"options").split(&"|") text = text.replace(&"[[%s]]" % found.get_string(&"options"), options[randi_range(0, options.size() - 1)]) # Do a pass on the markers to find any conditionals - var markers: ResolvedLineData = parser.extract_markers(text) + var markers: DMResolvedLineData = DMResolvedLineData.new(text) # Resolve any conditionals and update marker positions as needed - if data.type == DialogueConstants.TYPE_DIALOGUE: + if data.type == DMConstants.TYPE_DIALOGUE: var resolved_text: String = markers.text - var conditionals: Array[RegExMatch] = parser.INLINE_CONDITIONALS_REGEX.search_all(resolved_text) + var conditionals: Array[RegExMatch] = compilation.regex.INLINE_CONDITIONALS_REGEX.search_all(resolved_text) var replacements: Array = [] for conditional in conditionals: var condition_raw: String = conditional.strings[conditional.names.condition] @@ -175,9 +326,9 @@ func get_resolved_line_data(data: Dictionary, extra_game_states: Array = []) -> var bits = body.split(&"[else]") body = bits[0] body_else = bits[1] - var condition: Dictionary = parser.extract_condition("if " + condition_raw, false, 0) + var condition: Dictionary = compilation.extract_condition("if " + condition_raw, false, 0) # If the condition fails then use the else of "" - if not await check_condition({ condition = condition }, extra_game_states): + if not await _check_condition({ condition = condition }, extra_game_states): body = body_else replacements.append({ start = conditional.get_start(), @@ -186,7 +337,7 @@ func get_resolved_line_data(data: Dictionary, extra_game_states: Array = []) -> body = body }) - for i in range(replacements.size() -1, -1, -1): + for i in range(replacements.size() - 1, -1, -1): var r: Dictionary = replacements[i] resolved_text = resolved_text.substr(0, r.start) + r.body + resolved_text.substr(r.end, 9999) # Move any other markers now that the text has changed @@ -213,8 +364,6 @@ func get_resolved_line_data(data: Dictionary, extra_game_states: Array = []) -> markers.text = resolved_text - parser.free() - return markers @@ -224,7 +373,7 @@ func get_resolved_character(data: Dictionary, extra_game_states: Array = []) -> # Resolve variables for replacement in data.get(&"character_replacements", []): - var value = await resolve(replacement.expression.duplicate(true), extra_game_states) + var value = await _resolve(replacement.expression.duplicate(true), extra_game_states) var index: int = character.find(replacement.value_in_text) if index > -1: character = character.substr(0, index) + str(value) + character.substr(index + replacement.value_in_text.length()) @@ -241,48 +390,43 @@ func get_resolved_character(data: Dictionary, extra_game_states: Array = []) -> ## Generate a dialogue resource on the fly from some text func create_resource_from_text(text: String) -> Resource: - var parser: DialogueManagerParser = DialogueManagerParser.new() - parser.parse(text, "") - var results: DialogueManagerParseResult = parser.get_data() - var errors: Array[Dictionary] = parser.get_errors() - parser.free() + var result: DMCompilerResult = DMCompiler.compile_string(text, "") - if errors.size() > 0: - printerr(DialogueConstants.translate(&"runtime.errors").format({ count = errors.size() })) - for error in errors: - printerr(DialogueConstants.translate(&"runtime.error_detail").format({ + if result.errors.size() > 0: + printerr(DMConstants.translate(&"runtime.errors").format({ count = result.errors.size() })) + for error in result.errors: + printerr(DMConstants.translate(&"runtime.error_detail").format({ line = error.line_number + 1, - message = DialogueConstants.get_error_message(error.error) + message = DMConstants.get_error_message(error.error) })) - assert(false, DialogueConstants.translate(&"runtime.errors_see_details").format({ count = errors.size() })) + assert(false, DMConstants.translate(&"runtime.errors_see_details").format({ count = result.errors.size() })) var resource: DialogueResource = DialogueResource.new() - resource.using_states = results.using_states - resource.titles = results.titles - resource.first_title = results.first_title - resource.character_names = results.character_names - resource.lines = results.lines + resource.using_states = result.using_states + resource.titles = result.titles + resource.first_title = result.first_title + resource.character_names = result.character_names + resource.lines = result.lines resource.raw_text = text return resource +#region Balloon helpers + + ## Show the example balloon func show_example_dialogue_balloon(resource: DialogueResource, title: String = "", extra_game_states: Array = []) -> CanvasLayer: var balloon: Node = load(_get_example_balloon_path()).instantiate() - get_current_scene.call().add_child(balloon) - balloon.start(resource, title, extra_game_states) - dialogue_started.emit(resource) - + _start_balloon.call_deferred(balloon, resource, title, extra_game_states) return balloon ## Show the configured dialogue balloon func show_dialogue_balloon(resource: DialogueResource, title: String = "", extra_game_states: Array = []) -> Node: - var balloon_path: String = DialogueSettings.get_setting(&"balloon_path", _get_example_balloon_path()) + var balloon_path: String = DMSettings.get_setting(DMSettings.BALLOON_PATH, _get_example_balloon_path()) if not ResourceLoader.exists(balloon_path): balloon_path = _get_example_balloon_path() - dialogue_started.emit(resource) return show_dialogue_balloon_scene(balloon_path, resource, title, extra_game_states) @@ -294,14 +438,23 @@ func show_dialogue_balloon_scene(balloon_scene, resource: DialogueResource, titl balloon_scene = balloon_scene.instantiate() var balloon: Node = balloon_scene - get_current_scene.call().add_child.call_deferred(balloon) + _start_balloon.call_deferred(balloon, resource, title, extra_game_states) + return balloon + + +# Call "start" on the given balloon. +func _start_balloon(balloon: Node, resource: DialogueResource, title: String, extra_game_states: Array) -> void: + get_current_scene.call().add_child(balloon) + if balloon.has_method(&"start"): balloon.start(resource, title, extra_game_states) elif balloon.has_method(&"Start"): balloon.Start(resource, title, extra_game_states) else: - assert(false, DialogueConstants.translate(&"runtime.dialogue_balloon_missing_start_method")) - return balloon + assert(false, DMConstants.translate(&"runtime.dialogue_balloon_missing_start_method")) + + dialogue_started.emit(resource) + bridge_dialogue_started.emit(resource) # Get the path to the example balloon @@ -311,15 +464,32 @@ func _get_example_balloon_path() -> String: return get_script().resource_path.get_base_dir() + balloon_path -### Dotnet bridge +#endregion + +#region dotnet bridge -func _get_dotnet_dialogue_manager() -> Node: - return load(get_script().resource_path.get_base_dir() + "/DialogueManager.cs").new() +func _get_dotnet_dialogue_manager() -> RefCounted: + if not is_instance_valid(_dotnet_dialogue_manager): + _dotnet_dialogue_manager = load(get_script().resource_path.get_base_dir() + "/DialogueManager.cs").new() + return _dotnet_dialogue_manager func _bridge_get_new_instance() -> Node: - return new() + # For some reason duplicating the node with its signals doesn't work so we have to copy them over manually + var instance = new() + for s: Dictionary in dialogue_started.get_connections(): + instance.dialogue_started.connect(s.callable) + for s: Dictionary in passed_title.get_connections(): + instance.passed_title.connect(s.callable) + for s: Dictionary in got_dialogue.get_connections(): + instance.got_dialogue.connect(s.callable) + for s: Dictionary in mutated.get_connections(): + instance.mutated.connect(s.callable) + for s: Dictionary in dialogue_ended.get_connections(): + instance.dialogue_ended.connect(s.callable) + instance.get_current_scene = get_current_scene + return instance func _bridge_get_next_dialogue_line(resource: DialogueResource, key: String, extra_game_states: Array = []) -> void: @@ -331,127 +501,20 @@ func _bridge_get_next_dialogue_line(resource: DialogueResource, key: String, ext func _bridge_mutate(mutation: Dictionary, extra_game_states: Array, is_inline_mutation: bool = false) -> void: - await mutate(mutation, extra_game_states, is_inline_mutation) + await _mutate(mutation, extra_game_states, is_inline_mutation) bridge_mutated.emit() -### Helpers +#endregion - -# Get a line by its ID -func get_line(resource: DialogueResource, key: String, extra_game_states: Array) -> DialogueLine: - key = key.strip_edges() - - # See if we were given a stack instead of just the one key - var stack: Array = key.split("|") - key = stack.pop_front() - var id_trail: String = "" if stack.size() == 0 else "|" + "|".join(stack) - - # Key is blank so just use the first title - if key == null or key == "": - key = resource.first_title - - # See if we just ended the conversation - if key in [DialogueConstants.ID_END, DialogueConstants.ID_NULL, null]: - if stack.size() > 0: - return await get_line(resource, "|".join(stack), extra_game_states) - else: - return null - elif key == DialogueConstants.ID_END_CONVERSATION: - return null - - # See if it is a title - if key.begins_with("~ "): - key = key.substr(2) - if resource.titles.has(key): - key = resource.titles.get(key) - - if key in resource.titles.values(): - passed_title.emit(resource.titles.find_key(key)) - - if not resource.lines.has(key): - assert(false, DialogueConstants.translate(&"errors.key_not_found").format({ key = key })) - - var data: Dictionary = resource.lines.get(key) - - # This title key points to another title key so we should jump there instead - if data.type == DialogueConstants.TYPE_TITLE and data.next_id in resource.titles.values(): - return await get_line(resource, data.next_id + id_trail, extra_game_states) - - # Check for weighted random lines - if data.has(&"siblings"): - # Only count siblings that pass their condition (if they have one) - var successful_siblings: Array = data.siblings.filter(func(sibling): return not sibling.has("condition") or await check_condition(sibling, extra_game_states)) - var target_weight: float = randf_range(0, successful_siblings.reduce(func(total, sibling): return total + sibling.weight, 0)) - var cummulative_weight: float = 0 - for sibling in successful_siblings: - if target_weight < cummulative_weight + sibling.weight: - data = resource.lines.get(sibling.id) - break - else: - cummulative_weight += sibling.weight - - # Check condtiions - if data.type == DialogueConstants.TYPE_CONDITION: - # "else" will have no actual condition - if await check_condition(data, extra_game_states): - return await get_line(resource, data.next_id + id_trail, extra_game_states) - else: - return await get_line(resource, data.next_conditional_id + id_trail, extra_game_states) - - # Evaluate jumps - elif data.type == DialogueConstants.TYPE_GOTO: - if data.is_snippet and not id_trail.begins_with("|" + data.next_id_after): - id_trail = "|" + data.next_id_after + id_trail - return await get_line(resource, data.next_id + id_trail, extra_game_states) - - elif data.type == DialogueConstants.TYPE_DIALOGUE: - if not data.has(&"id"): - data.id = key - - # Set up a line object - var line: DialogueLine = await create_dialogue_line(data, extra_game_states) - - # If the jump point somehow has no content then just end - if not line: return null - - # If we are the first of a list of responses then get the other ones - if data.type == DialogueConstants.TYPE_RESPONSE: - # Note: For some reason C# has occasional issues with using the responses property directly - # so instead we use set and get here. - line.set(&"responses", await get_responses(data.get(&"responses", []), resource, id_trail, extra_game_states)) - return line - - # Inject the next node's responses if they have any - if resource.lines.has(line.next_id): - var next_line: Dictionary = resource.lines.get(line.next_id) - - # If the response line is marked as a title then make sure to emit the passed_title signal. - if line.next_id in resource.titles.values(): - passed_title.emit(resource.titles.find_key(line.next_id)) - - # If the responses come from a snippet then we need to come back here afterwards - if next_line.type == DialogueConstants.TYPE_GOTO and next_line.is_snippet and not id_trail.begins_with("|" + next_line.next_id_after): - id_trail = "|" + next_line.next_id_after + id_trail - - # If the next line is a title then check where it points to see if that is a set of responses. - while [DialogueConstants.TYPE_TITLE, DialogueConstants.TYPE_GOTO].has(next_line.type) and resource.lines.has(next_line.next_id): - next_line = resource.lines.get(next_line.next_id) - - if next_line != null and next_line.type == DialogueConstants.TYPE_RESPONSE: - # Note: For some reason C# has occasional issues with using the responses property directly - # so instead we use set and get here. - line.set(&"responses", await get_responses(next_line.get(&"responses", []), resource, id_trail, extra_game_states)) - - line.next_id = "|".join(stack) if line.next_id == DialogueConstants.ID_NULL else line.next_id + id_trail - return line +#region Internal helpers # Show a message or crash with error func show_error_for_missing_state_value(message: String, will_show: bool = true) -> void: if not will_show: return - if DialogueSettings.get_setting(&"ignore_missing_state_values", false): + if DMSettings.get_setting(DMSettings.IGNORE_MISSING_STATE_VALUES, false): push_error(message) elif will_show: # If you're here then you're missing a method or property in your game state. The error @@ -461,48 +524,50 @@ func show_error_for_missing_state_value(message: String, will_show: bool = true) # Translate a string func translate(data: Dictionary) -> String: - if translation_source == TranslationSource.None: + if translation_source == DMConstants.TranslationSource.None: return data.text - if data.translation_key == "" or data.translation_key == data.text: + var translation_key: String = data.get(&"translation_key", data.text) + + if translation_key == "" or translation_key == data.text: return tr(data.text) else: # Line IDs work slightly differently depending on whether the translation came from a # CSV or a PO file. CSVs use the line ID (or the line itself) as the translatable string # whereas POs use the ID as context and the line itself as the translatable string. match translation_source: - TranslationSource.PO: - return tr(data.text, StringName(data.translation_key)) + DMConstants.TranslationSource.PO: + return tr(data.text, StringName(translation_key)) - TranslationSource.CSV: - return tr(data.translation_key) + DMConstants.TranslationSource.CSV: + return tr(translation_key) - TranslationSource.Guess: + DMConstants.TranslationSource.Guess: var translation_files: Array = ProjectSettings.get_setting(&"internationalization/locale/translations") if translation_files.filter(func(f: String): return f.get_extension() in [&"po", &"mo"]).size() > 0: # Assume PO - return tr(data.text, StringName(data.translation_key)) + return tr(data.text, StringName(translation_key)) else: # Assume CSV - return tr(data.translation_key) + return tr(translation_key) - return tr(data.translation_key) + return tr(translation_key) # Create a line of dialogue func create_dialogue_line(data: Dictionary, extra_game_states: Array) -> DialogueLine: match data.type: - DialogueConstants.TYPE_DIALOGUE: - var resolved_data: ResolvedLineData = await get_resolved_line_data(data, extra_game_states) + DMConstants.TYPE_DIALOGUE: + var resolved_data: DMResolvedLineData = await get_resolved_line_data(data, extra_game_states) return DialogueLine.new({ id = data.get(&"id", ""), - type = DialogueConstants.TYPE_DIALOGUE, + type = DMConstants.TYPE_DIALOGUE, next_id = data.next_id, character = await get_resolved_character(data, extra_game_states), - character_replacements = data.character_replacements, + character_replacements = data.get(&"character_replacements", [] as Array[Dictionary]), text = resolved_data.text, - text_replacements = data.text_replacements, - translation_key = data.translation_key, + text_replacements = data.get(&"text_replacements", [] as Array[Dictionary]), + translation_key = data.get(&"translation_key", data.text), pauses = resolved_data.pauses, speeds = resolved_data.speeds, inline_mutations = resolved_data.mutations, @@ -511,19 +576,19 @@ func create_dialogue_line(data: Dictionary, extra_game_states: Array) -> Dialogu extra_game_states = extra_game_states }) - DialogueConstants.TYPE_RESPONSE: + DMConstants.TYPE_RESPONSE: return DialogueLine.new({ id = data.get(&"id", ""), - type = DialogueConstants.TYPE_RESPONSE, + type = DMConstants.TYPE_RESPONSE, next_id = data.next_id, tags = data.get(&"tags", []), extra_game_states = extra_game_states }) - DialogueConstants.TYPE_MUTATION: + DMConstants.TYPE_MUTATION: return DialogueLine.new({ id = data.get(&"id", ""), - type = DialogueConstants.TYPE_MUTATION, + type = DMConstants.TYPE_MUTATION, next_id = data.next_id, mutation = data.mutation, extra_game_states = extra_game_states @@ -534,23 +599,23 @@ func create_dialogue_line(data: Dictionary, extra_game_states: Array) -> Dialogu # Create a response func create_response(data: Dictionary, extra_game_states: Array) -> DialogueResponse: - var resolved_data: ResolvedLineData = await get_resolved_line_data(data, extra_game_states) + var resolved_data: DMResolvedLineData = await get_resolved_line_data(data, extra_game_states) return DialogueResponse.new({ id = data.get(&"id", ""), - type = DialogueConstants.TYPE_RESPONSE, + type = DMConstants.TYPE_RESPONSE, next_id = data.next_id, is_allowed = data.is_allowed, character = await get_resolved_character(data, extra_game_states), character_replacements = data.get(&"character_replacements", [] as Array[Dictionary]), text = resolved_data.text, - text_replacements = data.text_replacements, + text_replacements = data.get(&"text_replacements", [] as Array[Dictionary]), tags = data.get(&"tags", []), - translation_key = data.translation_key + translation_key = data.get(&"translation_key", data.text) }) # Get the current game states -func get_game_states(extra_game_states: Array) -> Array: +func _get_game_states(extra_game_states: Array) -> Array: if not _has_loaded_autoloads: _has_loaded_autoloads = true # Add any autoloads to a generic state so we can refer to them by name @@ -563,7 +628,7 @@ func get_game_states(extra_game_states: Array) -> Array: _autoloads[child.name] = child game_states = [_autoloads] # Add any other state shortcuts from settings - for node_name in DialogueSettings.get_setting(&"states", []): + for node_name in DMSettings.get_setting(DMSettings.STATE_AUTOLOAD_SHORTCUTS, ""): var state: Node = Engine.get_main_loop().root.get_node_or_null(node_name) if state: game_states.append(state) @@ -577,75 +642,97 @@ func get_game_states(extra_game_states: Array) -> Array: # Check if a condition is met -func check_condition(data: Dictionary, extra_game_states: Array) -> bool: +func _check_condition(data: Dictionary, extra_game_states: Array) -> bool: + return bool(await _resolve_condition_value(data, extra_game_states)) + + +# Resolve a condition's expression value +func _resolve_condition_value(data: Dictionary, extra_game_states: Array) -> Variant: if data.get(&"condition", null) == null: return true if data.condition.is_empty(): return true - return await resolve(data.condition.expression.duplicate(true), extra_game_states) + return await _resolve(data.condition.expression.duplicate(true), extra_game_states) + + +# Check if a match value matches a case value +func _check_case_value(match_value: Variant, data: Dictionary, extra_game_states: Array) -> bool: + if data.get(&"condition", null) == null: return true + if data.condition.is_empty(): return true + + var expression: Array[Dictionary] = data.condition.expression.duplicate(true) + + # If the when is a comparison when insert the match value as the first value to compare to + var already_compared: bool = false + if expression[0].type == DMConstants.TOKEN_COMPARISON: + expression.insert(0, { + type = DMConstants.TOKEN_VALUE, + value = match_value + }) + already_compared = true + + var resolved_value = await _resolve(expression, extra_game_states) + + if already_compared: + return resolved_value + else: + return match_value == resolved_value # Make a change to game state or run a method -func mutate(mutation: Dictionary, extra_game_states: Array, is_inline_mutation: bool = false) -> void: +func _mutate(mutation: Dictionary, extra_game_states: Array, is_inline_mutation: bool = false) -> void: var expression: Array[Dictionary] = mutation.expression # Handle built in mutations - if expression[0].type == DialogueConstants.TOKEN_FUNCTION and expression[0].function in [&"wait", &"debug"]: - var args: Array = await resolve_each(expression[0].value, extra_game_states) + if expression[0].type == DMConstants.TOKEN_FUNCTION and expression[0].function in [&"wait", &"Wait", &"debug", &"Debug"]: + var args: Array = await _resolve_each(expression[0].value, extra_game_states) match expression[0].function: - &"wait": - mutated.emit(mutation) + &"wait", &"Wait": + mutated.emit(mutation.merged({ is_inline = is_inline_mutation })) await Engine.get_main_loop().create_timer(float(args[0])).timeout return - &"debug": + &"debug", &"Debug": prints("Debug:", args) await Engine.get_main_loop().process_frame # Or pass through to the resolver else: - if not mutation_contains_assignment(mutation.expression) and not is_inline_mutation: - mutated.emit(mutation) + if not _mutation_contains_assignment(mutation.expression) and not is_inline_mutation: + mutated.emit(mutation.merged({ is_inline = is_inline_mutation })) if mutation.get("is_blocking", true): - await resolve(mutation.expression.duplicate(true), extra_game_states) + await _resolve(mutation.expression.duplicate(true), extra_game_states) return else: - resolve(mutation.expression.duplicate(true), extra_game_states) + _resolve(mutation.expression.duplicate(true), extra_game_states) # Wait one frame to give the dialogue handler a chance to yield await Engine.get_main_loop().process_frame -func mutation_contains_assignment(mutation: Array) -> bool: +# Check if a mutation contains an assignment token. +func _mutation_contains_assignment(mutation: Array) -> bool: for token in mutation: - if token.type == DialogueConstants.TOKEN_ASSIGNMENT: + if token.type == DMConstants.TOKEN_ASSIGNMENT: return true return false -func resolve_each(array: Array, extra_game_states: Array) -> Array: - var results: Array = [] - for item in array: - results.append(await resolve(item.duplicate(true), extra_game_states)) - return results - - # Replace an array of line IDs with their response prompts -func get_responses(ids: Array, resource: DialogueResource, id_trail: String, extra_game_states: Array) -> Array[DialogueResponse]: +func _get_responses(ids: Array, resource: DialogueResource, id_trail: String, extra_game_states: Array) -> Array[DialogueResponse]: var responses: Array[DialogueResponse] = [] for id in ids: var data: Dictionary = resource.lines.get(id).duplicate(true) - data.is_allowed = await check_condition(data, extra_game_states) - if DialogueSettings.get_setting(&"include_all_responses", false) or data.is_allowed: - var response: DialogueResponse = await create_response(data, extra_game_states) - response.next_id += id_trail - responses.append(response) + data.is_allowed = await _check_condition(data, extra_game_states) + var response: DialogueResponse = await create_response(data, extra_game_states) + response.next_id += id_trail + responses.append(response) return responses # Get a value on the current scene or game state -func get_state_value(property: String, extra_game_states: Array): +func _get_state_value(property: String, extra_game_states: Array): # Special case for static primitive calls if property == "Color": return Color() @@ -660,9 +747,12 @@ func get_state_value(property: String, extra_game_states: Array): var expression = Expression.new() if expression.parse(property) != OK: - assert(false, DialogueConstants.translate(&"runtime.invalid_expression").format({ expression = property, error = expression.get_error_text() })) + assert(false, DMConstants.translate(&"runtime.invalid_expression").format({ expression = property, error = expression.get_error_text() })) - for state in get_game_states(extra_game_states): + # Warn about possible name collisions + _warn_about_state_name_collisions(property, extra_game_states) + + for state in _get_game_states(extra_game_states): if typeof(state) == TYPE_DICTIONARY: if state.has(property): return state.get(property) @@ -679,152 +769,211 @@ func get_state_value(property: String, extra_game_states: Array): if class_data.get(&"class") == property: return load(class_data.path).new() - show_error_for_missing_state_value(DialogueConstants.translate(&"runtime.property_not_found").format({ property = property, states = _get_state_shortcut_names(extra_game_states) })) + show_error_for_missing_state_value(DMConstants.translate(&"runtime.property_not_found").format({ property = property, states = _get_state_shortcut_names(extra_game_states) })) + + +# Print warnings for top-level state name collisions. +func _warn_about_state_name_collisions(target_key: String, extra_game_states: Array) -> void: + # Don't run the check if this is a release build + if not OS.is_debug_build(): return + # Also don't run if the setting is off + if not DMSettings.get_setting(DMSettings.WARN_ABOUT_METHOD_PROPERTY_OR_SIGNAL_NAME_CONFLICTS, false): return + + # Get the list of state shortcuts. + var state_shortcuts: Array = [] + for node_name in DMSettings.get_setting(DMSettings.STATE_AUTOLOAD_SHORTCUTS, ""): + var state: Node = Engine.get_main_loop().root.get_node_or_null(node_name) + if state: + state_shortcuts.append(state) + + # Check any top level names for a collision + var states_with_key: Array = [] + for state in extra_game_states + [get_current_scene.call()] + state_shortcuts: + if state is Dictionary: + if state.keys().has(target_key): + states_with_key.append("Dictionary") + else: + var script: Script = (state as Object).get_script() + if script == null: + continue + + for method in script.get_script_method_list(): + if method.name == target_key and not states_with_key.has(state.name): + states_with_key.append(state.name) + break + + for property in script.get_script_property_list(): + if property.name == target_key and not states_with_key.has(state.name): + states_with_key.append(state.name) + break + + for signal_info in script.get_script_signal_list(): + if signal_info.name == target_key and not states_with_key.has(state.name): + states_with_key.append(state.name) + break + + if states_with_key.size() > 1: + push_warning(DMConstants.translate(&"runtime.top_level_states_share_name").format({ states = ", ".join(states_with_key), key = target_key })) # Set a value on the current scene or game state -func set_state_value(property: String, value, extra_game_states: Array) -> void: - for state in get_game_states(extra_game_states): +func _set_state_value(property: String, value, extra_game_states: Array) -> void: + for state in _get_game_states(extra_game_states): if typeof(state) == TYPE_DICTIONARY: if state.has(property): state[property] = value return - elif thing_has_property(state, property): + elif _thing_has_property(state, property): state.set(property, value) return if property.to_snake_case() != property: - show_error_for_missing_state_value(DialogueConstants.translate(&"runtime.property_not_found_missing_export").format({ property = property, states = _get_state_shortcut_names(extra_game_states) })) + show_error_for_missing_state_value(DMConstants.translate(&"runtime.property_not_found_missing_export").format({ property = property, states = _get_state_shortcut_names(extra_game_states) })) else: - show_error_for_missing_state_value(DialogueConstants.translate(&"runtime.property_not_found").format({ property = property, states = _get_state_shortcut_names(extra_game_states) })) + show_error_for_missing_state_value(DMConstants.translate(&"runtime.property_not_found").format({ property = property, states = _get_state_shortcut_names(extra_game_states) })) # Get the list of state shortcut names func _get_state_shortcut_names(extra_game_states: Array) -> String: - var states = get_game_states(extra_game_states) + var states = _get_game_states(extra_game_states) states.erase(_autoloads) return ", ".join(states.map(func(s): return "\"%s\"" % (s.name if "name" in s else s))) -# Collapse any expressions -func resolve(tokens: Array, extra_game_states: Array): - # Handle groups first - for token in tokens: - if token.type == DialogueConstants.TOKEN_GROUP: - token["type"] = "value" - token["value"] = await resolve(token.value, extra_game_states) +# Resolve an array of expressions. +func _resolve_each(array: Array, extra_game_states: Array) -> Array: + var results: Array = [] + for item in array: + if not item[0].type in [DMConstants.TOKEN_BRACE_CLOSE, DMConstants.TOKEN_BRACKET_CLOSE, DMConstants.TOKEN_PARENS_CLOSE]: + results.append(await _resolve(item.duplicate(true), extra_game_states)) + return results - # Then variables/methods + +# Collapse any expressions +func _resolve(tokens: Array, extra_game_states: Array): var i: int = 0 var limit: int = 0 + + # Handle groups first + for token in tokens: + if token.type == DMConstants.TOKEN_GROUP: + token.type = DMConstants.TOKEN_VALUE + token.value = await _resolve(token.value, extra_game_states) + + # Then variables/methods + i = 0 + limit = 0 while i < tokens.size() and limit < 1000: limit += 1 var token: Dictionary = tokens[i] - if token.type == DialogueConstants.TOKEN_FUNCTION: + if token.type == DMConstants.TOKEN_FUNCTION: var function_name: String = token.function - var args = await resolve_each(token.value, extra_game_states) - if tokens[i - 1].type == DialogueConstants.TOKEN_DOT: + var args = await _resolve_each(token.value, extra_game_states) + if tokens[i - 1].type == DMConstants.TOKEN_DOT: # If we are calling a deeper function then we need to collapse the # value into the thing we are calling the function on var caller: Dictionary = tokens[i - 2] if Builtins.is_supported(caller.value): - caller["type"] = "value" - caller["value"] = Builtins.resolve_method(caller.value, function_name, args) + caller.type = DMConstants.TOKEN_VALUE + caller.value = Builtins.resolve_method(caller.value, function_name, args) tokens.remove_at(i) - tokens.remove_at(i-1) + tokens.remove_at(i - 1) i -= 2 - elif thing_has_method(caller.value, function_name, args): - caller["type"] = "value" - caller["value"] = await resolve_thing_method(caller.value, function_name, args) + elif _thing_has_method(caller.value, function_name, args): + caller.type = DMConstants.TOKEN_VALUE + caller.value = await _resolve_thing_method(caller.value, function_name, args) tokens.remove_at(i) - tokens.remove_at(i-1) + tokens.remove_at(i - 1) i -= 2 else: - show_error_for_missing_state_value(DialogueConstants.translate(&"runtime.method_not_callable").format({ method = function_name, object = str(caller.value) })) + show_error_for_missing_state_value(DMConstants.translate(&"runtime.method_not_callable").format({ method = function_name, object = str(caller.value) })) else: var found: bool = false match function_name: &"str": - token["type"] = "value" - token["value"] = str(args[0]) + token.type = DMConstants.TOKEN_VALUE + token.value = str(args[0]) found = true &"Vector2": - token["type"] = "value" - token["value"] = Vector2(args[0], args[1]) + token.type = DMConstants.TOKEN_VALUE + token.value = Vector2(args[0], args[1]) found = true &"Vector2i": - token["type"] = "value" - token["value"] = Vector2i(args[0], args[1]) + token.type = DMConstants.TOKEN_VALUE + token.value = Vector2i(args[0], args[1]) found = true &"Vector3": - token["type"] = "value" - token["value"] = Vector3(args[0], args[1], args[2]) + token.type = DMConstants.TOKEN_VALUE + token.value = Vector3(args[0], args[1], args[2]) found = true &"Vector3i": - token["type"] = "value" - token["value"] = Vector3i(args[0], args[1], args[2]) + token.type = DMConstants.TOKEN_VALUE + token.value = Vector3i(args[0], args[1], args[2]) found = true &"Vector4": - token["type"] = "value" - token["value"] = Vector4(args[0], args[1], args[2], args[3]) + token.type = DMConstants.TOKEN_VALUE + token.value = Vector4(args[0], args[1], args[2], args[3]) found = true &"Vector4i": - token["type"] = "value" - token["value"] = Vector4i(args[0], args[1], args[2], args[3]) + token.type = DMConstants.TOKEN_VALUE + token.value = Vector4i(args[0], args[1], args[2], args[3]) found = true &"Quaternion": - token["type"] = "value" - token["value"] = Quaternion(args[0], args[1], args[2], args[3]) + token.type = DMConstants.TOKEN_VALUE + token.value = Quaternion(args[0], args[1], args[2], args[3]) found = true &"Callable": - token["type"] = "value" + token.type = DMConstants.TOKEN_VALUE match args.size(): 0: - token["value"] = Callable() + token.value = Callable() 1: - token["value"] = Callable(args[0]) + token.value = Callable(args[0]) 2: - token["value"] = Callable(args[0], args[1]) + token.value = Callable(args[0], args[1]) found = true &"Color": - token["type"] = "value" + token.type = DMConstants.TOKEN_VALUE match args.size(): 0: - token["value"] = Color() + token.value = Color() 1: - token["value"] = Color(args[0]) + token.value = Color(args[0]) 2: - token["value"] = Color(args[0], args[1]) + token.value = Color(args[0], args[1]) 3: - token["value"] = Color(args[0], args[1], args[2]) + token.value = Color(args[0], args[1], args[2]) 4: - token["value"] = Color(args[0], args[1], args[2], args[3]) + token.value = Color(args[0], args[1], args[2], args[3]) found = true - &"load": - token["type"] = "value" - token["value"] = load(args[0]) + &"load", &"Load": + token.type = DMConstants.TOKEN_VALUE + token.value = load(args[0]) found = true - &"emit": - token["type"] = "value" - token["value"] = resolve_signal(args, extra_game_states) + &"roll_dice", &"RollDice": + token.type = DMConstants.TOKEN_VALUE + token.value = randi_range(1, args[0]) found = true _: - for state in get_game_states(extra_game_states): - if thing_has_method(state, function_name, args): - token["type"] = "value" - token["value"] = await resolve_thing_method(state, function_name, args) + # Check for top level name conflicts + _warn_about_state_name_collisions(function_name, extra_game_states) + + for state in _get_game_states(extra_game_states): + if _thing_has_method(state, function_name, args): + token.type = DMConstants.TOKEN_VALUE + token.value = await _resolve_thing_method(state, function_name, args) found = true break - show_error_for_missing_state_value(DialogueConstants.translate(&"runtime.method_not_found").format({ + show_error_for_missing_state_value(DMConstants.translate(&"runtime.method_not_found").format({ method = args[0] if function_name in ["call", "call_deferred"] else function_name, states = _get_state_shortcut_names(extra_game_states) }), not found) - elif token.type == DialogueConstants.TOKEN_DICTIONARY_REFERENCE: + elif token.type == DMConstants.TOKEN_DICTIONARY_REFERENCE: var value - if i > 0 and tokens[i - 1].type == DialogueConstants.TOKEN_DOT: + if i > 0 and tokens[i - 1].type == DMConstants.TOKEN_DOT: # If we are deep referencing then we need to get the parent object. # `parent.value` is the actual object and `token.variable` is the name of # the property within it. @@ -836,123 +985,126 @@ func resolve(tokens: Array, extra_game_states: Array): i -= 2 else: # Otherwise we can just get this variable as a normal state reference - value = get_state_value(token.variable, extra_game_states) + value = _get_state_value(token.variable, extra_game_states) - var index = await resolve(token.value, extra_game_states) + var index = await _resolve(token.value, extra_game_states) if typeof(value) == TYPE_DICTIONARY: - if tokens.size() > i + 1 and tokens[i + 1].type == DialogueConstants.TOKEN_ASSIGNMENT: + if tokens.size() > i + 1 and tokens[i + 1].type == DMConstants.TOKEN_ASSIGNMENT: # If the next token is an assignment then we need to leave this as a reference # so that it can be resolved once everything ahead of it has been resolved - token["type"] = "dictionary" - token["value"] = value - token["key"] = index + token.type = "dictionary" + token.value = value + token.key = index else: if value.has(index): - token["type"] = "value" - token["value"] = value[index] + token.type = DMConstants.TOKEN_VALUE + token.value = value[index] else: - show_error_for_missing_state_value(DialogueConstants.translate(&"runtime.key_not_found").format({ key = str(index), dictionary = token.variable })) + show_error_for_missing_state_value(DMConstants.translate(&"runtime.key_not_found").format({ key = str(index), dictionary = token.variable })) elif typeof(value) == TYPE_ARRAY: - if tokens.size() > i + 1 and tokens[i + 1].type == DialogueConstants.TOKEN_ASSIGNMENT: + if tokens.size() > i + 1 and tokens[i + 1].type == DMConstants.TOKEN_ASSIGNMENT: # If the next token is an assignment then we need to leave this as a reference # so that it can be resolved once everything ahead of it has been resolved - token["type"] = "array" - token["value"] = value - token["key"] = index + token.type = "array" + token.value = value + token.key = index else: if index >= 0 and index < value.size(): - token["type"] = "value" - token["value"] = value[index] + token.type = DMConstants.TOKEN_VALUE + token.value = value[index] else: - show_error_for_missing_state_value(DialogueConstants.translate(&"runtime.array_index_out_of_bounds").format({ index = index, array = token.variable })) + show_error_for_missing_state_value(DMConstants.translate(&"runtime.array_index_out_of_bounds").format({ index = index, array = token.variable })) - elif token.type == DialogueConstants.TOKEN_DICTIONARY_NESTED_REFERENCE: + elif token.type == DMConstants.TOKEN_DICTIONARY_NESTED_REFERENCE: var dictionary: Dictionary = tokens[i - 1] - var index = await resolve(token.value, extra_game_states) + var index = await _resolve(token.value, extra_game_states) var value = dictionary.value if typeof(value) == TYPE_DICTIONARY: - if tokens.size() > i + 1 and tokens[i + 1].type == DialogueConstants.TOKEN_ASSIGNMENT: + if tokens.size() > i + 1 and tokens[i + 1].type == DMConstants.TOKEN_ASSIGNMENT: # If the next token is an assignment then we need to leave this as a reference # so that it can be resolved once everything ahead of it has been resolved - dictionary["type"] = "dictionary" - dictionary["key"] = index - dictionary["value"] = value + dictionary.type = "dictionary" + dictionary.key = index + dictionary.value = value tokens.remove_at(i) i -= 1 else: if dictionary.value.has(index): - dictionary["value"] = value.get(index) + dictionary.value = value.get(index) tokens.remove_at(i) i -= 1 else: - show_error_for_missing_state_value(DialogueConstants.translate(&"runtime.key_not_found").format({ key = str(index), dictionary = value })) + show_error_for_missing_state_value(DMConstants.translate(&"runtime.key_not_found").format({ key = str(index), dictionary = value })) elif typeof(value) == TYPE_ARRAY: - if tokens.size() > i + 1 and tokens[i + 1].type == DialogueConstants.TOKEN_ASSIGNMENT: + if tokens.size() > i + 1 and tokens[i + 1].type == DMConstants.TOKEN_ASSIGNMENT: # If the next token is an assignment then we need to leave this as a reference # so that it can be resolved once everything ahead of it has been resolved - dictionary["type"] = "array" - dictionary["value"] = value - dictionary["key"] = index + dictionary.type = "array" + dictionary.value = value + dictionary.key = index tokens.remove_at(i) i -= 1 else: if index >= 0 and index < value.size(): - dictionary["value"] = value[index] + dictionary.value = value[index] tokens.remove_at(i) i -= 1 else: - show_error_for_missing_state_value(DialogueConstants.translate(&"runtime.array_index_out_of_bounds").format({ index = index, array = value })) + show_error_for_missing_state_value(DMConstants.translate(&"runtime.array_index_out_of_bounds").format({ index = index, array = value })) - elif token.type == DialogueConstants.TOKEN_ARRAY: - token["type"] = "value" - token["value"] = await resolve_each(token.value, extra_game_states) + elif token.type == DMConstants.TOKEN_ARRAY: + token.type = DMConstants.TOKEN_VALUE + token.value = await _resolve_each(token.value, extra_game_states) - elif token.type == DialogueConstants.TOKEN_DICTIONARY: - token["type"] = "value" + elif token.type == DMConstants.TOKEN_DICTIONARY: + token.type = DMConstants.TOKEN_VALUE var dictionary = {} for key in token.value.keys(): - var resolved_key = await resolve([key], extra_game_states) + var resolved_key = await _resolve([key], extra_game_states) var preresolved_value = token.value.get(key) if typeof(preresolved_value) != TYPE_ARRAY: preresolved_value = [preresolved_value] - var resolved_value = await resolve(preresolved_value, extra_game_states) + var resolved_value = await _resolve(preresolved_value, extra_game_states) dictionary[resolved_key] = resolved_value - token["value"] = dictionary + token.value = dictionary - elif token.type == DialogueConstants.TOKEN_VARIABLE or token.type == DialogueConstants.TOKEN_NUMBER: + elif token.type == DMConstants.TOKEN_VARIABLE or token.type == DMConstants.TOKEN_NUMBER: if str(token.value) == "null": - token["type"] = "value" - token["value"] = null - elif tokens[i - 1].type == DialogueConstants.TOKEN_DOT: + token.type = DMConstants.TOKEN_VALUE + token.value = null + elif str(token.value) == "self": + token.type = DMConstants.TOKEN_VALUE + token.value = extra_game_states[0].self + elif tokens[i - 1].type == DMConstants.TOKEN_DOT: var caller: Dictionary = tokens[i - 2] var property = token.value - if tokens.size() > i + 1 and tokens[i + 1].type == DialogueConstants.TOKEN_ASSIGNMENT: + if tokens.size() > i + 1 and tokens[i + 1].type == DMConstants.TOKEN_ASSIGNMENT: # If the next token is an assignment then we need to leave this as a reference # so that it can be resolved once everything ahead of it has been resolved - caller["type"] = "property" - caller["property"] = property + caller.type = "property" + caller.property = property else: # If we are requesting a deeper property then we need to collapse the # value into the thing we are referencing from - caller["type"] = "value" + caller.type = DMConstants.TOKEN_VALUE if Builtins.is_supported(caller.value): - caller["value"] = Builtins.resolve_property(caller.value, property) + caller.value = Builtins.resolve_property(caller.value, property) else: - caller["value"] = caller.value.get(property) + caller.value = caller.value.get(property) tokens.remove_at(i) - tokens.remove_at(i-1) + tokens.remove_at(i - 1) i -= 2 - elif tokens.size() > i + 1 and tokens[i + 1].type == DialogueConstants.TOKEN_ASSIGNMENT: + elif tokens.size() > i + 1 and tokens[i + 1].type == DMConstants.TOKEN_ASSIGNMENT: # It's a normal variable but we will be assigning to it so don't resolve # it until everything after it has been resolved - token["type"] = "variable" + token.type = "variable" else: - if token.type == DialogueConstants.TOKEN_NUMBER: - token["type"] = "value" - token["value"] = token.value + if token.type == DMConstants.TOKEN_NUMBER: + token.type = DMConstants.TOKEN_VALUE + token.value = token.value else: - token["type"] = "value" - token["value"] = get_state_value(str(token.value), extra_game_states) + token.type = DMConstants.TOKEN_VALUE + token.value = _get_state_value(str(token.value), extra_game_states) i += 1 @@ -962,16 +1114,16 @@ func resolve(tokens: Array, extra_game_states: Array): while i < tokens.size() and limit < 1000: limit += 1 var token: Dictionary = tokens[i] - if token.type == DialogueConstants.TOKEN_OPERATOR and token.value in ["*", "/", "%"]: - token["type"] = "value" - token["value"] = apply_operation(token.value, tokens[i-1].value, tokens[i+1].value) - tokens.remove_at(i+1) - tokens.remove_at(i-1) + if token.type == DMConstants.TOKEN_OPERATOR and token.value in ["*", "/", "%"]: + token.type = DMConstants.TOKEN_VALUE + token.value = _apply_operation(token.value, tokens[i - 1].value, tokens[i + 1].value) + tokens.remove_at(i + 1) + tokens.remove_at(i - 1) i -= 1 i += 1 if limit >= 1000: - assert(false, DialogueConstants.translate(&"runtime.something_went_wrong")) + assert(false, DMConstants.translate(&"runtime.something_went_wrong")) # Then addition and subtraction i = 0 @@ -979,16 +1131,16 @@ func resolve(tokens: Array, extra_game_states: Array): while i < tokens.size() and limit < 1000: limit += 1 var token: Dictionary = tokens[i] - if token.type == DialogueConstants.TOKEN_OPERATOR and token.value in ["+", "-"]: - token["type"] = "value" - token["value"] = apply_operation(token.value, tokens[i-1].value, tokens[i+1].value) - tokens.remove_at(i+1) - tokens.remove_at(i-1) + if token.type == DMConstants.TOKEN_OPERATOR and token.value in ["+", "-"]: + token.type = DMConstants.TOKEN_VALUE + token.value = _apply_operation(token.value, tokens[i - 1].value, tokens[i + 1].value) + tokens.remove_at(i + 1) + tokens.remove_at(i - 1) i -= 1 i += 1 if limit >= 1000: - assert(false, DialogueConstants.translate(&"runtime.something_went_wrong")) + assert(false, DMConstants.translate(&"runtime.something_went_wrong")) # Then negations i = 0 @@ -996,15 +1148,15 @@ func resolve(tokens: Array, extra_game_states: Array): while i < tokens.size() and limit < 1000: limit += 1 var token: Dictionary = tokens[i] - if token.type == DialogueConstants.TOKEN_NOT: - token["type"] = "value" - token["value"] = not tokens[i+1].value - tokens.remove_at(i+1) + if token.type == DMConstants.TOKEN_NOT: + token.type = DMConstants.TOKEN_VALUE + token.value = not tokens[i + 1].value + tokens.remove_at(i + 1) i -= 1 i += 1 if limit >= 1000: - assert(false, DialogueConstants.translate(&"runtime.something_went_wrong")) + assert(false, DMConstants.translate(&"runtime.something_went_wrong")) # Then comparisons i = 0 @@ -1012,16 +1164,16 @@ func resolve(tokens: Array, extra_game_states: Array): while i < tokens.size() and limit < 1000: limit += 1 var token: Dictionary = tokens[i] - if token.type == DialogueConstants.TOKEN_COMPARISON: - token["type"] = "value" - token["value"] = compare(token.value, tokens[i-1].value, tokens[i+1].value) - tokens.remove_at(i+1) - tokens.remove_at(i-1) + if token.type == DMConstants.TOKEN_COMPARISON: + token.type = DMConstants.TOKEN_VALUE + token.value = _compare(token.value, tokens[i - 1].value, tokens[i + 1].value) + tokens.remove_at(i + 1) + tokens.remove_at(i - 1) i -= 1 i += 1 if limit >= 1000: - assert(false, DialogueConstants.translate(&"runtime.something_went_wrong")) + assert(false, DMConstants.translate(&"runtime.something_went_wrong")) # Then and/or i = 0 @@ -1029,16 +1181,16 @@ func resolve(tokens: Array, extra_game_states: Array): while i < tokens.size() and limit < 1000: limit += 1 var token: Dictionary = tokens[i] - if token.type == DialogueConstants.TOKEN_AND_OR: - token["type"] = "value" - token["value"] = apply_operation(token.value, tokens[i-1].value, tokens[i+1].value) - tokens.remove_at(i+1) - tokens.remove_at(i-1) + if token.type == DMConstants.TOKEN_AND_OR: + token.type = DMConstants.TOKEN_VALUE + token.value = _apply_operation(token.value, tokens[i - 1].value, tokens[i + 1].value) + tokens.remove_at(i + 1) + tokens.remove_at(i - 1) i -= 1 i += 1 if limit >= 1000: - assert(false, DialogueConstants.translate(&"runtime.something_went_wrong")) + assert(false, DMConstants.translate(&"runtime.something_went_wrong")) # Lastly, resolve any assignments i = 0 @@ -1046,47 +1198,48 @@ func resolve(tokens: Array, extra_game_states: Array): while i < tokens.size() and limit < 1000: limit += 1 var token: Dictionary = tokens[i] - if token.type == DialogueConstants.TOKEN_ASSIGNMENT: + if token.type == DMConstants.TOKEN_ASSIGNMENT: var lhs: Dictionary = tokens[i - 1] var value match lhs.type: &"variable": - value = apply_operation(token.value, get_state_value(lhs.value, extra_game_states), tokens[i+1].value) - set_state_value(lhs.value, value, extra_game_states) + value = _apply_operation(token.value, _get_state_value(lhs.value, extra_game_states), tokens[i + 1].value) + _set_state_value(lhs.value, value, extra_game_states) &"property": - value = apply_operation(token.value, lhs.value.get(lhs.property), tokens[i+1].value) + value = _apply_operation(token.value, lhs.value.get(lhs.property), tokens[i + 1].value) if typeof(lhs.value) == TYPE_DICTIONARY: lhs.value[lhs.property] = value else: lhs.value.set(lhs.property, value) &"dictionary": - value = apply_operation(token.value, lhs.value.get(lhs.key, null), tokens[i+1].value) + value = _apply_operation(token.value, lhs.value.get(lhs.key, null), tokens[i + 1].value) lhs.value[lhs.key] = value &"array": show_error_for_missing_state_value( - DialogueConstants.translate(&"runtime.array_index_out_of_bounds").format({ index = lhs.key, array = lhs.value }), + DMConstants.translate(&"runtime.array_index_out_of_bounds").format({ index = lhs.key, array = lhs.value }), lhs.key >= lhs.value.size() ) - value = apply_operation(token.value, lhs.value[lhs.key], tokens[i+1].value) + value = _apply_operation(token.value, lhs.value[lhs.key], tokens[i + 1].value) lhs.value[lhs.key] = value _: - show_error_for_missing_state_value(DialogueConstants.translate(&"runtime.left_hand_size_cannot_be_assigned_to")) + show_error_for_missing_state_value(DMConstants.translate(&"runtime.left_hand_size_cannot_be_assigned_to")) - token["type"] = "value" - token["value"] = value - tokens.remove_at(i+1) - tokens.remove_at(i-1) + token.type = DMConstants.TOKEN_VALUE + token.value = value + tokens.remove_at(i + 1) + tokens.remove_at(i - 1) i -= 1 i += 1 if limit >= 1000: - assert(false, DialogueConstants.translate(&"runtime.something_went_wrong")) + assert(false, DMConstants.translate(&"runtime.something_went_wrong")) return tokens[0].value -func compare(operator: String, first_value, second_value) -> bool: +# Compare two values. +func _compare(operator: String, first_value, second_value) -> bool: match operator: &"in": if first_value == null or second_value == null: @@ -1141,7 +1294,8 @@ func compare(operator: String, first_value, second_value) -> bool: return false -func apply_operation(operator: String, first_value, second_value): +# Apply an operation from one value to another. +func _apply_operation(operator: String, first_value, second_value): match operator: &"=": return second_value @@ -1160,23 +1314,26 @@ func apply_operation(operator: String, first_value, second_value): &"or": return first_value or second_value - assert(false, DialogueConstants.translate(&"runtime.unknown_operator")) + assert(false, DMConstants.translate(&"runtime.unknown_operator")) -# Check if a dialogue line contains meaningful information -func is_valid(line: DialogueLine) -> bool: +# Check if a dialogue line contains meaningful information. +func _is_valid(line: DialogueLine) -> bool: if line == null: return false - if line.type == DialogueConstants.TYPE_MUTATION and line.mutation == null: + if line.type == DMConstants.TYPE_MUTATION and line.mutation == null: return false - if line.type == DialogueConstants.TYPE_RESPONSE and line.get(&"responses").size() == 0: + if line.type == DMConstants.TYPE_RESPONSE and line.get(&"responses").size() == 0: return false return true -func thing_has_method(thing, method: String, args: Array) -> bool: - if Builtins.is_supported(thing): +# Check that a thing has a given method. +func _thing_has_method(thing, method: String, args: Array) -> bool: + if Builtins.is_supported(thing, method): return thing != _autoloads + elif thing is Dictionary: + return false if method in [&"call", &"call_deferred"]: return thing.has_method(args[0]) @@ -1187,15 +1344,15 @@ func thing_has_method(thing, method: String, args: Array) -> bool: if thing.has_method(method): return true - if method.to_snake_case() != method and DialogueSettings.check_for_dotnet_solution(): + if method.to_snake_case() != method and DMSettings.check_for_dotnet_solution(): # If we get this far then the method might be a C# method with a Task return type - return _get_dotnet_dialogue_manager().ThingHasMethod(thing, method) + return _get_dotnet_dialogue_manager().ThingHasMethod(thing, method, args) return false # Check if a given property exists -func thing_has_property(thing: Object, property: String) -> bool: +func _thing_has_property(thing: Object, property: String) -> bool: if thing == null: return false @@ -1209,50 +1366,26 @@ func thing_has_property(thing: Object, property: String) -> bool: return false -func resolve_signal(args: Array, extra_game_states: Array): - if args[0] is Signal: - args[0] = args[0].get_name() - - for state in get_game_states(extra_game_states): - if typeof(state) == TYPE_DICTIONARY: - continue - elif state.has_signal(args[0]): - match args.size(): - 1: - state.emit_signal(args[0]) - 2: - state.emit_signal(args[0], args[1]) - 3: - state.emit_signal(args[0], args[1], args[2]) - 4: - state.emit_signal(args[0], args[1], args[2], args[3]) - 5: - state.emit_signal(args[0], args[1], args[2], args[3], args[4]) - 6: - state.emit_signal(args[0], args[1], args[2], args[3], args[4], args[5]) - 7: - state.emit_signal(args[0], args[1], args[2], args[3], args[4], args[5], args[6]) - 8: - state.emit_signal(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]) - return - - # The signal hasn't been found anywhere - show_error_for_missing_state_value(DialogueConstants.translate(&"runtime.signal_not_found").format({ signal_name = args[0], states = _get_state_shortcut_names(extra_game_states) })) - - -func get_method_info_for(thing, method: String) -> Dictionary: +func _get_method_info_for(thing: Variant, method: String, args: Array) -> Dictionary: # Use the thing instance id as a key for the caching dictionary. var thing_instance_id: int = thing.get_instance_id() if not _method_info_cache.has(thing_instance_id): var methods: Dictionary = {} for m in thing.get_method_list(): - methods[m.name] = m + methods["%s:%d" % [m.name, m.args.size()]] = m + if not methods.has(m.name): + methods[m.name] = m _method_info_cache[thing_instance_id] = methods - return _method_info_cache.get(thing_instance_id, {}).get(method) + var methods: Dictionary = _method_info_cache.get(thing_instance_id, {}) + var method_key: String = "%s:%d" % [method, args.size()] + if methods.has(method_key): + return methods.get(method_key) + else: + return methods.get(method) -func resolve_thing_method(thing, method: String, args: Array): +func _resolve_thing_method(thing, method: String, args: Array): if Builtins.is_supported(thing): var result = Builtins.resolve_method(thing, method, args) if not Builtins.has_resolve_method_failed(): @@ -1260,13 +1393,13 @@ func resolve_thing_method(thing, method: String, args: Array): if thing.has_method(method): # Try to convert any literals to the right type - var method_info: Dictionary = get_method_info_for(thing, method) + var method_info: Dictionary = _get_method_info_for(thing, method, args) var method_args: Array = method_info.args if method_info.flags & METHOD_FLAG_VARARG == 0 and method_args.size() < args.size(): - assert(false, DialogueConstants.translate(&"runtime.expected_n_got_n_args").format({ expected = method_args.size(), method = method, received = args.size()})) + assert(false, DMConstants.translate(&"runtime.expected_n_got_n_args").format({ expected = method_args.size(), method = method, received = args.size()})) for i in range(0, min(method_args.size(), args.size())): var m: Dictionary = method_args[i] - var to_type:int = typeof(args[i]) + var to_type: int = typeof(args[i]) if m.type == TYPE_ARRAY: match m.hint_string: &"String": @@ -1281,7 +1414,7 @@ func resolve_thing_method(thing, method: String, args: Array): to_type = TYPE_PACKED_VECTOR3_ARRAY _: if m.hint_string != "": - assert(false, DialogueConstants.translate(&"runtime.unsupported_array_type").format({ type = m.hint_string})) + assert(false, DMConstants.translate(&"runtime.unsupported_array_type").format({ type = m.hint_string})) if typeof(args[i]) != to_type: args[i] = convert(args[i], to_type) diff --git a/addons/dialogue_manager/dialogue_manager.gd.uid b/addons/dialogue_manager/dialogue_manager.gd.uid new file mode 100644 index 00000000..d10762e9 --- /dev/null +++ b/addons/dialogue_manager/dialogue_manager.gd.uid @@ -0,0 +1 @@ +uid://c3rodes2l3gxb diff --git a/addons/dialogue_manager/dialogue_resource.gd b/addons/dialogue_manager/dialogue_resource.gd index 1020ce8f..29ade7b1 100644 --- a/addons/dialogue_manager/dialogue_resource.gd +++ b/addons/dialogue_manager/dialogue_resource.gd @@ -5,7 +5,6 @@ class_name DialogueResource extends Resource -const _DialogueManager = preload("./dialogue_manager.gd") const DialogueLine = preload("./dialogue_line.gd") ## A list of state shortcuts @@ -30,7 +29,7 @@ const DialogueLine = preload("./dialogue_line.gd") ## Get the next printable line of dialogue, starting from a referenced line ([code]title[/code] can ## be a title string or a stringified line number). Runs any mutations along the way and then returns ## the first dialogue line encountered. -func get_next_dialogue_line(title: String, extra_game_states: Array = [], mutation_behaviour: _DialogueManager.MutationBehaviour = _DialogueManager.MutationBehaviour.Wait) -> DialogueLine: +func get_next_dialogue_line(title: String = "", extra_game_states: Array = [], mutation_behaviour: DMConstants.MutationBehaviour = DMConstants.MutationBehaviour.Wait) -> DialogueLine: return await Engine.get_singleton("DialogueManager").get_next_dialogue_line(self, title, extra_game_states, mutation_behaviour) @@ -39,23 +38,5 @@ func get_titles() -> PackedStringArray: return titles.keys() -func get_ordered_titles() -> Array: - var splitted = raw_text.split("\n") - var ordered_titles = [] - for line in splitted: - if line.begins_with("~ "): - ordered_titles.append(line.substr(2).strip_edges()) - # # check ordered_titles consistency to titles - # for title in ordered_titles: - # if not titles.has(title): - # printerr("Title %s not found in titles" % title) - # ordered_titles.remove(title) - # for title in titles.keys(): - # if not ordered_titles.has(title): - # printerr("Title %s not found in ordered_titles" % title) - # ordered_titles.append(title) - return ordered_titles - - func _to_string() -> String: return "<DialogueResource titles=\"%s\">" % [",".join(titles.keys())] diff --git a/addons/dialogue_manager/dialogue_resource.gd.uid b/addons/dialogue_manager/dialogue_resource.gd.uid new file mode 100644 index 00000000..27b95d09 --- /dev/null +++ b/addons/dialogue_manager/dialogue_resource.gd.uid @@ -0,0 +1 @@ +uid://dbs4435dsf3ry diff --git a/addons/dialogue_manager/dialogue_response.gd b/addons/dialogue_manager/dialogue_response.gd index 92cec241..701ce926 100644 --- a/addons/dialogue_manager/dialogue_response.gd +++ b/addons/dialogue_manager/dialogue_response.gd @@ -2,14 +2,11 @@ class_name DialogueResponse extends RefCounted -const _DialogueConstants = preload("./constants.gd") - - ## The ID of this response var id: String ## The internal type of this dialogue object, always set to [code]TYPE_RESPONSE[/code]. -var type: String = _DialogueConstants.TYPE_RESPONSE +var type: String = DMConstants.TYPE_RESPONSE ## The next line ID to use if this response is selected by the player. var next_id: String = "" diff --git a/addons/dialogue_manager/dialogue_response.gd.uid b/addons/dialogue_manager/dialogue_response.gd.uid new file mode 100644 index 00000000..9b4532ae --- /dev/null +++ b/addons/dialogue_manager/dialogue_response.gd.uid @@ -0,0 +1 @@ +uid://cm0xpfeywpqid diff --git a/addons/dialogue_manager/dialogue_reponses_menu.gd b/addons/dialogue_manager/dialogue_responses_menu.gd similarity index 91% rename from addons/dialogue_manager/dialogue_reponses_menu.gd rename to addons/dialogue_manager/dialogue_responses_menu.gd index d666b2ef..cd66ae5b 100644 --- a/addons/dialogue_manager/dialogue_reponses_menu.gd +++ b/addons/dialogue_manager/dialogue_responses_menu.gd @@ -14,6 +14,9 @@ signal response_selected(response) ## The action for accepting a response (is possibly overridden by parent dialogue balloon). @export var next_action: StringName = &"" +## Hide any responses where [code]is_allowed[/code] is false +@export var hide_failed_responses: bool = false + ## The list of dialogue responses. var responses: Array = []: get: @@ -31,6 +34,8 @@ var responses: Array = []: # Add new items if responses.size() > 0: for response in responses: + if hide_failed_responses and not response.is_allowed: continue + var item: Control if is_instance_valid(response_template): item = response_template.duplicate(DUPLICATE_GROUPS | DUPLICATE_SCRIPTS | DUPLICATE_SIGNALS) @@ -39,7 +44,7 @@ var responses: Array = []: item = Button.new() item.name = "Response%d" % get_child_count() if not response.is_allowed: - item.name = String(item.name) + "Disallowed" + item.name = item.name + &"Disallowed" item.disabled = true # If the item has a response property then use that @@ -59,7 +64,9 @@ var responses: Array = []: func _ready() -> void: visibility_changed.connect(func(): if visible and get_menu_items().size() > 0: - get_menu_items()[0].grab_focus() + var first_item: Control = get_menu_items()[0] + if first_item.is_inside_tree(): + first_item.grab_focus() ) if is_instance_valid(response_template): @@ -77,11 +84,6 @@ func get_menu_items() -> Array: return items -## [b]DEPRECATED[/b]. Do not use. -func set_responses(next_responses: Array) -> void: - self.responses = next_responses - - #region Internal diff --git a/addons/dialogue_manager/dialogue_responses_menu.gd.uid b/addons/dialogue_manager/dialogue_responses_menu.gd.uid new file mode 100644 index 00000000..0ae73d9d --- /dev/null +++ b/addons/dialogue_manager/dialogue_responses_menu.gd.uid @@ -0,0 +1 @@ +uid://bb52rsfwhkxbn diff --git a/addons/dialogue_manager/editor_translation_parser_plugin.gd b/addons/dialogue_manager/editor_translation_parser_plugin.gd index 16a18338..137ab700 100644 --- a/addons/dialogue_manager/editor_translation_parser_plugin.gd +++ b/addons/dialogue_manager/editor_translation_parser_plugin.gd @@ -1,43 +1,52 @@ -extends EditorTranslationParserPlugin +class_name DMTranslationParserPlugin extends EditorTranslationParserPlugin -const DialogueConstants = preload("./constants.gd") -const DialogueSettings = preload("./settings.gd") -const DialogueManagerParser = preload("./components/parser.gd") -const DialogueManagerParseResult = preload("./components/parse_result.gd") +## Cached result of parsing a dialogue file. +var data: DMCompilerResult +## List of characters that were added. +var translated_character_names: PackedStringArray = [] +var translated_lines: Array[Dictionary] = [] -func _parse_file(path: String, msgids: Array, msgids_context_plural: Array) -> void: +func _parse_file(path: String) -> Array[PackedStringArray]: + var msgs: Array[PackedStringArray] = [] var file: FileAccess = FileAccess.open(path, FileAccess.READ) var text: String = file.get_as_text() - var data: DialogueManagerParseResult = DialogueManagerParser.parse_string(text, path) + data = DMCompiler.compile_string(text, path) + var known_keys: PackedStringArray = PackedStringArray([]) # Add all character names if settings ask for it - if DialogueSettings.get_setting("export_characters_in_translation", true): - var character_names: PackedStringArray = data.character_names - for character_name in character_names: + if DMSettings.get_setting(DMSettings.INCLUDE_CHARACTERS_IN_TRANSLATABLE_STRINGS_LIST, true): + translated_character_names = [] as Array[DialogueLine] + for character_name: String in data.character_names: if character_name in known_keys: continue known_keys.append(character_name) - msgids_context_plural.append([character_name.replace('"', '\"'), "dialogue", ""]) + translated_character_names.append(character_name) + msgs.append(PackedStringArray([character_name.replace('"', '\"'), "dialogue", "", DMConstants.translate("translation_plugin.character_name")])) # Add all dialogue lines and responses var dialogue: Dictionary = data.lines - for key in dialogue.keys(): + for key: String in dialogue.keys(): var line: Dictionary = dialogue.get(key) - if not line.type in [DialogueConstants.TYPE_DIALOGUE, DialogueConstants.TYPE_RESPONSE]: continue - if line.translation_key in known_keys: continue + if not line.type in [DMConstants.TYPE_DIALOGUE, DMConstants.TYPE_RESPONSE]: continue - known_keys.append(line.translation_key) + var translation_key: String = line.get(&"translation_key", line.text) - if line.translation_key == "" or line.translation_key == line.text: - msgids_context_plural.append([line.text.replace('"', '\"'), "", ""]) + if translation_key in known_keys: continue + + known_keys.append(translation_key) + translated_lines.append(line) + if translation_key == line.text: + msgs.append(PackedStringArray([line.text.replace('"', '\"'), "", "", line.get("notes", "")])) else: - msgids_context_plural.append([line.text.replace('"', '\"'), line.translation_key.replace('"', '\"'), ""]) + msgs.append(PackedStringArray([line.text.replace('"', '\"'), line.translation_key.replace('"', '\"'), "", line.get("notes", "")])) + + return msgs func _get_recognized_extensions() -> PackedStringArray: diff --git a/addons/dialogue_manager/editor_translation_parser_plugin.gd.uid b/addons/dialogue_manager/editor_translation_parser_plugin.gd.uid new file mode 100644 index 00000000..22ddbe98 --- /dev/null +++ b/addons/dialogue_manager/editor_translation_parser_plugin.gd.uid @@ -0,0 +1 @@ +uid://c6bya881h1egb diff --git a/addons/dialogue_manager/example_balloon/ExampleBalloon.cs b/addons/dialogue_manager/example_balloon/ExampleBalloon.cs index 28358040..980f0679 100644 --- a/addons/dialogue_manager/example_balloon/ExampleBalloon.cs +++ b/addons/dialogue_manager/example_balloon/ExampleBalloon.cs @@ -25,10 +25,6 @@ namespace DialogueManagerRuntime get => dialogueLine; set { - isWaitingForInput = false; - balloon.FocusMode = Control.FocusModeEnum.All; - balloon.GrabFocus(); - if (value == null) { QueueFree(); @@ -36,10 +32,12 @@ namespace DialogueManagerRuntime } dialogueLine = value; - UpdateDialogue(); + ApplyDialogueLine(); } } + Timer MutationCooldown = new Timer(); + public override void _Ready() { @@ -88,6 +86,18 @@ namespace DialogueManagerRuntime Next(response.NextId); })); + + // Hide the balloon when a mutation is running + MutationCooldown.Timeout += () => + { + if (willHideBalloon) + { + willHideBalloon = false; + balloon.Hide(); + } + }; + AddChild(MutationCooldown); + DialogueManager.Mutated += OnMutated; } @@ -122,12 +132,7 @@ namespace DialogueManagerRuntime public async void Start(Resource dialogueResource, string title, Array<Variant> extraGameStates = null) { - if (!IsNodeReady()) - { - await ToSignal(this, SignalName.Ready); - } - - temporaryGameStates = extraGameStates ?? new Array<Variant>(); + temporaryGameStates = new Array<Variant> { this } + (extraGameStates ?? new Array<Variant>()); isWaitingForInput = false; resource = dialogueResource; @@ -144,12 +149,13 @@ namespace DialogueManagerRuntime #region Helpers - private async void UpdateDialogue() + private async void ApplyDialogueLine() { - if (!IsNodeReady()) - { - await ToSignal(this, SignalName.Ready); - } + MutationCooldown.Stop(); + + isWaitingForInput = false; + balloon.FocusMode = Control.FocusModeEnum.All; + balloon.GrabFocus(); // Set up the character name characterLabel.Visible = !string.IsNullOrEmpty(dialogueLine.Character); @@ -208,14 +214,7 @@ namespace DialogueManagerRuntime { isWaitingForInput = false; willHideBalloon = true; - GetTree().CreateTimer(0.1f).Timeout += () => - { - if (willHideBalloon) - { - willHideBalloon = false; - balloon.Hide(); - } - }; + MutationCooldown.Start(0.1f); } diff --git a/addons/dialogue_manager/example_balloon/ExampleBalloon.cs.uid b/addons/dialogue_manager/example_balloon/ExampleBalloon.cs.uid new file mode 100644 index 00000000..4b3783a5 --- /dev/null +++ b/addons/dialogue_manager/example_balloon/ExampleBalloon.cs.uid @@ -0,0 +1 @@ +uid://5b3w40kwakl3 diff --git a/addons/dialogue_manager/example_balloon/example_balloon.gd b/addons/dialogue_manager/example_balloon/example_balloon.gd index a3dc9df1..c7e6d9a0 100644 --- a/addons/dialogue_manager/example_balloon/example_balloon.gd +++ b/addons/dialogue_manager/example_balloon/example_balloon.gd @@ -26,55 +26,19 @@ var _locale: String = TranslationServer.get_locale() ## The current line var dialogue_line: DialogueLine: - set(next_dialogue_line): - is_waiting_for_input = false - balloon.focus_mode = Control.FOCUS_ALL - balloon.grab_focus() - - # The dialogue has finished so close the balloon - if not next_dialogue_line: - queue_free() - return - - # If the node isn't ready yet then none of the labels will be ready yet either - if not is_node_ready(): - await ready - - dialogue_line = next_dialogue_line - - character_label.visible = not dialogue_line.character.is_empty() - character_label.text = tr(dialogue_line.character, "dialogue") - - dialogue_label.hide() - dialogue_label.dialogue_line = dialogue_line - - responses_menu.hide() - responses_menu.set_responses(dialogue_line.responses) - - # Show our balloon - balloon.show() - will_hide_balloon = false - - dialogue_label.show() - if not dialogue_line.text.is_empty(): - dialogue_label.type_out() - await dialogue_label.finished_typing - - # Wait for input - if dialogue_line.responses.size() > 0: - balloon.focus_mode = Control.FOCUS_NONE - responses_menu.show() - elif dialogue_line.time != "": - var time = dialogue_line.text.length() * 0.02 if dialogue_line.time == "auto" else dialogue_line.time.to_float() - await get_tree().create_timer(time).timeout - next(dialogue_line.next_id) + set(value): + if value: + dialogue_line = value + apply_dialogue_line() else: - is_waiting_for_input = true - balloon.focus_mode = Control.FOCUS_ALL - balloon.grab_focus() + # The dialogue has finished so close the balloon + queue_free() get: return dialogue_line +## A cooldown timer for delaying the balloon hide when encountering a mutation. +var mutation_cooldown: Timer = Timer.new() + ## The base balloon anchor @onready var balloon: Control = %Balloon @@ -96,6 +60,9 @@ func _ready() -> void: if responses_menu.next_action.is_empty(): responses_menu.next_action = next_action + mutation_cooldown.timeout.connect(_on_mutation_cooldown_timeout) + add_child(mutation_cooldown) + func _unhandled_input(_event: InputEvent) -> void: # Only the balloon is allowed to handle input while it's showing @@ -114,14 +81,52 @@ func _notification(what: int) -> void: ## Start some dialogue func start(dialogue_resource: DialogueResource, title: String, extra_game_states: Array = []) -> void: - if not is_node_ready(): - await ready - temporary_game_states = [self] + extra_game_states + temporary_game_states = [self] + extra_game_states is_waiting_for_input = false resource = dialogue_resource self.dialogue_line = await resource.get_next_dialogue_line(title, temporary_game_states) +## Apply any changes to the balloon given a new [DialogueLine]. +func apply_dialogue_line() -> void: + mutation_cooldown.stop() + + is_waiting_for_input = false + balloon.focus_mode = Control.FOCUS_ALL + balloon.grab_focus() + + character_label.visible = not dialogue_line.character.is_empty() + character_label.text = tr(dialogue_line.character, "dialogue") + + dialogue_label.hide() + dialogue_label.dialogue_line = dialogue_line + + responses_menu.hide() + responses_menu.responses = dialogue_line.responses + + # Show our balloon + balloon.show() + will_hide_balloon = false + + dialogue_label.show() + if not dialogue_line.text.is_empty(): + dialogue_label.type_out() + await dialogue_label.finished_typing + + # Wait for input + if dialogue_line.responses.size() > 0: + balloon.focus_mode = Control.FOCUS_NONE + responses_menu.show() + elif dialogue_line.time != "": + var time = dialogue_line.text.length() * 0.02 if dialogue_line.time == "auto" else dialogue_line.time.to_float() + await get_tree().create_timer(time).timeout + next(dialogue_line.next_id) + else: + is_waiting_for_input = true + balloon.focus_mode = Control.FOCUS_ALL + balloon.grab_focus() + + ## Go to the next line func next(next_id: String) -> void: self.dialogue_line = await resource.get_next_dialogue_line(next_id, temporary_game_states) @@ -130,14 +135,16 @@ func next(next_id: String) -> void: #region Signals +func _on_mutation_cooldown_timeout() -> void: + if will_hide_balloon: + will_hide_balloon = false + balloon.hide() + + func _on_mutated(_mutation: Dictionary) -> void: is_waiting_for_input = false will_hide_balloon = true - get_tree().create_timer(0.1).timeout.connect(func(): - if will_hide_balloon: - will_hide_balloon = false - balloon.hide() - ) + mutation_cooldown.start(0.1) func _on_balloon_gui_input(event: InputEvent) -> void: diff --git a/addons/dialogue_manager/example_balloon/example_balloon.gd.uid b/addons/dialogue_manager/example_balloon/example_balloon.gd.uid new file mode 100644 index 00000000..6327f9b6 --- /dev/null +++ b/addons/dialogue_manager/example_balloon/example_balloon.gd.uid @@ -0,0 +1 @@ +uid://d1wt4ma6055l8 diff --git a/addons/dialogue_manager/example_balloon/example_balloon.tscn b/addons/dialogue_manager/example_balloon/example_balloon.tscn index 6facc260..91d8a7df 100644 --- a/addons/dialogue_manager/example_balloon/example_balloon.tscn +++ b/addons/dialogue_manager/example_balloon/example_balloon.tscn @@ -1,8 +1,8 @@ [gd_scene load_steps=9 format=3 uid="uid://73jm5qjy52vq"] -[ext_resource type="Script" path="res://addons/dialogue_manager/example_balloon/example_balloon.gd" id="1_36de5"] +[ext_resource type="Script" uid="uid://d1wt4ma6055l8" path="res://addons/dialogue_manager/example_balloon/example_balloon.gd" id="1_36de5"] [ext_resource type="PackedScene" uid="uid://ckvgyvclnwggo" path="res://addons/dialogue_manager/dialogue_label.tscn" id="2_a8ve6"] -[ext_resource type="Script" path="res://addons/dialogue_manager/dialogue_reponses_menu.gd" id="3_72ixx"] +[ext_resource type="Script" uid="uid://bb52rsfwhkxbn" path="res://addons/dialogue_manager/dialogue_responses_menu.gd" id="3_72ixx"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_spyqn"] bg_color = Color(0, 0, 0, 1) diff --git a/addons/dialogue_manager/example_balloon/small_example_balloon.tscn b/addons/dialogue_manager/example_balloon/small_example_balloon.tscn index 30118337..c4d2145f 100644 --- a/addons/dialogue_manager/example_balloon/small_example_balloon.tscn +++ b/addons/dialogue_manager/example_balloon/small_example_balloon.tscn @@ -1,8 +1,8 @@ [gd_scene load_steps=10 format=3 uid="uid://13s5spsk34qu"] -[ext_resource type="Script" path="res://addons/dialogue_manager/example_balloon/example_balloon.gd" id="1_s2gbs"] +[ext_resource type="Script" uid="uid://d1wt4ma6055l8" path="res://addons/dialogue_manager/example_balloon/example_balloon.gd" id="1_s2gbs"] [ext_resource type="PackedScene" uid="uid://ckvgyvclnwggo" path="res://addons/dialogue_manager/dialogue_label.tscn" id="2_hfvdi"] -[ext_resource type="Script" path="res://addons/dialogue_manager/dialogue_reponses_menu.gd" id="3_1j1j0"] +[ext_resource type="Script" uid="uid://bb52rsfwhkxbn" path="res://addons/dialogue_manager/dialogue_responses_menu.gd" id="3_1j1j0"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_235ry"] content_margin_left = 6.0 @@ -104,6 +104,7 @@ grow_vertical = 2 theme = SubResource("Theme_qq3yp") [node name="Panel" type="Panel" parent="Balloon"] +clip_children = 2 layout_mode = 1 anchors_preset = 12 anchor_top = 1.0 @@ -115,6 +116,7 @@ offset_right = -4.0 offset_bottom = -4.0 grow_horizontal = 2 grow_vertical = 0 +mouse_filter = 1 [node name="Dialogue" type="MarginContainer" parent="Balloon/Panel"] layout_mode = 1 @@ -142,7 +144,6 @@ unique_name_in_owner = true layout_mode = 2 size_flags_vertical = 3 text = "Dialogue..." -skip_pause_at_abbreviations = PackedStringArray("Mr", "Mrs", "Ms", "Dr", "etc", "eg", "ex") [node name="Responses" type="MarginContainer" parent="Balloon"] layout_mode = 1 diff --git a/addons/dialogue_manager/import_plugin.gd b/addons/dialogue_manager/import_plugin.gd index 4d3103e0..345fe844 100644 --- a/addons/dialogue_manager/import_plugin.gd +++ b/addons/dialogue_manager/import_plugin.gd @@ -1,20 +1,16 @@ @tool -extends EditorImportPlugin +class_name DMImportPlugin extends EditorImportPlugin signal compiled_resource(resource: Resource) -const DialogueResource = preload("./dialogue_resource.gd") -const DialogueManagerParser = preload("./components/parser.gd") -const DialogueManagerParseResult = preload("./components/parse_result.gd") - -const compiler_version = 13 +const COMPILER_VERSION = 14 func _get_importer_name() -> String: # NOTE: A change to this forces a re-import of all dialogue - return "dialogue_manager_compiler_%s" % compiler_version + return "dialogue_manager_compiler_%s" % COMPILER_VERSION func _get_visible_name() -> String: @@ -63,7 +59,7 @@ func _get_option_visibility(path: String, option_name: StringName, options: Dict func _import(source_file: String, save_path: String, options: Dictionary, platform_variants: Array[String], gen_files: Array[String]) -> Error: - var cache = Engine.get_meta("DialogueCache") + var cache = Engine.get_meta("DMCache") # Get the raw file contents if not FileAccess.file_exists(source_file): return ERR_FILE_NOT_FOUND @@ -73,17 +69,12 @@ func _import(source_file: String, save_path: String, options: Dictionary, platfo cache.file_content_changed.emit(source_file, raw_text) - # Parse the text - var parser: DialogueManagerParser = DialogueManagerParser.new() - var err: Error = parser.parse(raw_text, source_file) - var data: DialogueManagerParseResult = parser.get_data() - var errors: Array[Dictionary] = parser.get_errors() - parser.free() - - if err != OK: - printerr("%d errors found in %s" % [errors.size(), source_file]) - cache.add_errors_to_file(source_file, errors) - return err + # Compile the text + var result: DMCompilerResult = DMCompiler.compile_string(raw_text, source_file) + if result.errors.size() > 0: + printerr("%d errors found in %s" % [result.errors.size(), source_file]) + cache.add_errors_to_file(source_file, result.errors) + return ERR_PARSE_ERROR # Get the current addon version var config: ConfigFile = ConfigFile.new() @@ -94,17 +85,17 @@ func _import(source_file: String, save_path: String, options: Dictionary, platfo var resource: DialogueResource = DialogueResource.new() resource.set_meta("dialogue_manager_version", version) - resource.using_states = data.using_states - resource.titles = data.titles - resource.first_title = data.first_title - resource.character_names = data.character_names - resource.lines = data.lines - resource.raw_text = data.raw_text + resource.using_states = result.using_states + resource.titles = result.titles + resource.first_title = result.first_title + resource.character_names = result.character_names + resource.lines = result.lines + resource.raw_text = result.raw_text # Clear errors and possibly trigger any cascade recompiles - cache.add_file(source_file, data) + cache.add_file(source_file, result) - err = ResourceSaver.save(resource, "%s.%s" % [save_path, _get_save_extension()]) + var err: Error = ResourceSaver.save(resource, "%s.%s" % [save_path, _get_save_extension()]) compiled_resource.emit(resource) diff --git a/addons/dialogue_manager/import_plugin.gd.uid b/addons/dialogue_manager/import_plugin.gd.uid new file mode 100644 index 00000000..e98bfab8 --- /dev/null +++ b/addons/dialogue_manager/import_plugin.gd.uid @@ -0,0 +1 @@ +uid://dhwpj6ed8soyq diff --git a/addons/dialogue_manager/inspector_plugin.gd b/addons/dialogue_manager/inspector_plugin.gd index e3cb7e51..366c1f3a 100644 --- a/addons/dialogue_manager/inspector_plugin.gd +++ b/addons/dialogue_manager/inspector_plugin.gd @@ -1,5 +1,5 @@ @tool -extends EditorInspectorPlugin +class_name DMInspectorPlugin extends EditorInspectorPlugin const DialogueEditorProperty = preload("./components/editor_property/editor_property.gd") diff --git a/addons/dialogue_manager/inspector_plugin.gd.uid b/addons/dialogue_manager/inspector_plugin.gd.uid new file mode 100644 index 00000000..00c8db87 --- /dev/null +++ b/addons/dialogue_manager/inspector_plugin.gd.uid @@ -0,0 +1 @@ +uid://0x31sbqbikov diff --git a/addons/dialogue_manager/l10n/en.po b/addons/dialogue_manager/l10n/en.po index e4ef3b6a..bd9a7952 100644 --- a/addons/dialogue_manager/l10n/en.po +++ b/addons/dialogue_manager/l10n/en.po @@ -37,7 +37,10 @@ msgid "find_in_files" msgstr "Find in files..." msgid "test_dialogue" -msgstr "Test dialogue" +msgstr "Test dialogue from start of file" + +msgid "test_dialogue_from_line" +msgstr "Test dialogue from current line" msgid "search_for_text" msgstr "Search for text" @@ -48,9 +51,6 @@ msgstr "Insert" msgid "translations" msgstr "Translations" -msgid "settings" -msgstr "Settings" - msgid "sponsor" msgstr "Sponsor" @@ -144,84 +144,6 @@ msgstr "Copy file path" msgid "buffer.show_in_filesystem" msgstr "Show in FileSystem" -msgid "settings.invalid_test_scene" -msgstr "\"{path}\" does not extend BaseDialogueTestScene." - -msgid "settings.revert_to_default_test_scene" -msgstr "Revert to default test scene" - -msgid "settings.default_balloon_hint" -msgstr "Custom balloon to use when calling \"DialogueManager.show_balloon()\"" - -msgid "settings.revert_to_default_balloon" -msgstr "Revert to default balloon" - -msgid "settings.default_balloon_path" -msgstr "<example balloon>" - -msgid "settings.autoload" -msgstr "Autoload" - -msgid "settings.path" -msgstr "Path" - -msgid "settings.new_template" -msgstr "New dialogue files will start with template text" - -msgid "settings.missing_keys" -msgstr "Treat missing translation keys as errors" - -msgid "settings.missing_keys_hint" -msgstr "If you are using static translation keys then having this enabled will help you find any lines that you haven't added a key to yet." - -msgid "settings.characters_translations" -msgstr "Export character names in translation files" - -msgid "settings.wrap_long_lines" -msgstr "Wrap long lines" - -msgid "settings.include_failed_responses" -msgstr "Include responses with failed conditions" - -msgid "settings.ignore_missing_state_values" -msgstr "Skip over missing state value errors (not recommended)" - -msgid "settings.custom_test_scene" -msgstr "Custom test scene (must extend BaseDialogueTestScene)" - -msgid "settings.default_csv_locale" -msgstr "Default CSV Locale" - -msgid "settings.states_shortcuts" -msgstr "State Shortcuts" - -msgid "settings.states_message" -msgstr "If an autoload is enabled here you can refer to its properties, methods, and signals without having to use its name." - -msgid "settings.states_hint" -msgstr "ie. Instead of \"SomeState.some_property\" you could just use \"some_property\"" - -msgid "settings.recompile_warning" -msgstr "Changing these settings will force a recompile of all dialogue. Only change them if you know what you are doing." - -msgid "settings.create_lines_for_responses_with_characters" -msgstr "Create child dialogue line for responses with character names in them" - -msgid "settings.open_in_external_editor" -msgstr "Open dialogue files in external editor" - -msgid "settings.external_editor_warning" -msgstr "Note: Syntax highlighting and detailed error checking are not supported in external editors." - -msgid "settings.include_characters_in_translations" -msgstr "Include character names in translation exports" - -msgid "settings.include_notes_in_translations" -msgstr "Include notes (## comments) in translation exports" - -msgid "settings.check_for_updates" -msgstr "Check for updates" - msgid "n_of_n" msgstr "{index} of {total}" @@ -384,6 +306,21 @@ msgstr "Invalid index." msgid "errors.unexpected_assignment" msgstr "Unexpected assignment." +msgid "errors.expected_when_or_else" +msgstr "Expecting a when or an else case." + +msgid "errors.only_one_else_allowed" +msgstr "Only one else case is allowed per match." + +msgid "errors.when_must_belong_to_match" +msgstr "When statements can only appear as children of match statements." + +msgid "errors.concurrent_line_without_origin" +msgstr "Concurrent lines need an origin line that doesn't start with \"| \"." + +msgid "errors.goto_not_allowed_on_concurrect_lines" +msgstr "Goto references are not allowed on concurrent dialogue lines." + msgid "errors.unknown" msgstr "Unknown syntax." @@ -478,4 +415,10 @@ msgid "runtime.unsupported_array_type" msgstr "Array[{type}] isn't supported in mutations. Use Array as a type instead." msgid "runtime.dialogue_balloon_missing_start_method" -msgstr "Your dialogue balloon is missing a \"start\" or \"Start\" method." \ No newline at end of file +msgstr "Your dialogue balloon is missing a \"start\" or \"Start\" method." + +msgid "runtime.top_level_states_share_name" +msgstr "Multiple top-level states ({states}) share method/property/signal name \"{key}\". Only the first occurance is accessible to dialogue." + +msgid "translation_plugin.character_name" +msgstr "Character name" \ No newline at end of file diff --git a/addons/dialogue_manager/l10n/es.po b/addons/dialogue_manager/l10n/es.po index 3cbaa32e..ef604e1d 100644 --- a/addons/dialogue_manager/l10n/es.po +++ b/addons/dialogue_manager/l10n/es.po @@ -41,9 +41,6 @@ msgstr "Insertar" msgid "translations" msgstr "Traducciones" -msgid "settings" -msgstr "Ajustes" - msgid "show_support" msgstr "Contribuye con Dialogue Manager" @@ -134,82 +131,6 @@ msgstr "Copiar la ruta del archivo" msgid "buffer.show_in_filesystem" msgstr "Mostrar en el sistema de archivos" -msgid "settings.invalid_test_scene" -msgstr "\"{path}\" no extiende BaseDialogueTestScene." - -msgid "settings.revert_to_default_test_scene" -msgstr "Revertir a la escena de prueba por defecto" - -msgid "settings.default_balloon_hint" -msgstr "" -"Globo personalizado para usar al llamar a \"DialogueManager.show_balloon()\"" - -msgid "settings.revert_to_default_balloon" -msgstr "Volver al globo predeterminado" - -msgid "settings.default_balloon_path" -msgstr "<globo de ejemplo>" - -msgid "settings.autoload" -msgstr "Autocarga" - -msgid "settings.path" -msgstr "Ruta" - -msgid "settings.new_template" -msgstr "Los nuevos archivos de diálogo empezarán con una plantilla" - -msgid "settings.missing_keys" -msgstr "Tratar las claves de traducción faltantes como errores" - -msgid "settings.missing_keys_hint" -msgstr "Si estás utilizando claves de traducción estáticas, tener esta opción habilitada te ayudará a encontrar cualquier línea a la que aún no le hayas añadido una clave." - -msgid "settings.characters_translations" -msgstr "Exportar nombres de personajes en archivos de traducción" - -msgid "settings.wrap_long_lines" -msgstr "Romper líneas largas" - -msgid "settings.include_failed_responses" -msgstr "Incluir respuestas con condiciones fallidas" - -msgid "settings.ignore_missing_state_values" -msgstr "Omitir errores de valores de estado faltantes (no recomendado)" - -msgid "settings.custom_test_scene" -msgstr "Escena de prueba personalizada (debe extender BaseDialogueTestScene)" - -msgid "settings.default_csv_locale" -msgstr "Localización CSV por defecto" - -msgid "settings.states_shortcuts" -msgstr "Atajos de teclado" - -msgid "settings.states_message" -msgstr "Si un autoload está habilitado aquí, puedes referirte a sus propiedades y métodos sin tener que usar su nombre." - -msgid "settings.states_hint" -msgstr "ie. En lugar de \"SomeState.some_property\" podría simplemente usar \"some_property\"" - -msgid "settings.recompile_warning" -msgstr "Cambiar estos ajustes obligará a recompilar todo el diálogo. Hazlo solo si sabes lo que estás haciendo." - -msgid "settings.create_lines_for_responses_with_characters" -msgstr "Crear línea de diálogo para respuestas con nombres de personajes dentro." - -msgid "settings.open_in_external_editor" -msgstr "Abrir archivos de diálogo en el editor externo" - -msgid "settings.external_editor_warning" -msgstr "Nota: El resaltado de sintaxis y la verificación detallada de errores no están soportados en editores externos." - -msgid "settings.include_characters_in_translations" -msgstr "Incluir nombres de personajes en las exportaciones de traducción" - -msgid "settings.include_notes_in_translations" -msgstr "Incluir notas (## comentarios) en las exportaciones de traducción" - msgid "n_of_n" msgstr "{index} de {total}" diff --git a/addons/dialogue_manager/l10n/translations.pot b/addons/dialogue_manager/l10n/translations.pot index b6c3197e..795b4724 100644 --- a/addons/dialogue_manager/l10n/translations.pot +++ b/addons/dialogue_manager/l10n/translations.pot @@ -32,6 +32,9 @@ msgstr "" msgid "test_dialogue" msgstr "" +msgid "test_dialogue_from_line" +msgstr "" + msgid "search_for_text" msgstr "" @@ -41,9 +44,6 @@ msgstr "" msgid "translations" msgstr "" -msgid "settings" -msgstr "" - msgid "sponsor" msgstr "" @@ -134,84 +134,6 @@ msgstr "" msgid "buffer.show_in_filesystem" msgstr "" -msgid "settings.invalid_test_scene" -msgstr "" - -msgid "settings.revert_to_default_test_scene" -msgstr "" - -msgid "settings.default_balloon_hint" -msgstr "" - -msgid "settings.revert_to_default_balloon" -msgstr "" - -msgid "settings.default_balloon_path" -msgstr "" - -msgid "settings.autoload" -msgstr "" - -msgid "settings.path" -msgstr "" - -msgid "settings.new_template" -msgstr "" - -msgid "settings.missing_keys" -msgstr "" - -msgid "settings.missing_keys_hint" -msgstr "" - -msgid "settings.characters_translations" -msgstr "" - -msgid "settings.wrap_long_lines" -msgstr "" - -msgid "settings.include_failed_responses" -msgstr "" - -msgid "settings.ignore_missing_state_values" -msgstr "" - -msgid "settings.custom_test_scene" -msgstr "" - -msgid "settings.default_csv_locale" -msgstr "" - -msgid "settings.states_shortcuts" -msgstr "" - -msgid "settings.states_message" -msgstr "" - -msgid "settings.states_hint" -msgstr "" - -msgid "settings.recompile_warning" -msgstr "" - -msgid "settings.create_lines_for_responses_with_characters" -msgstr "" - -msgid "settings.open_in_external_editor" -msgstr "" - -msgid "settings.external_editor_warning" -msgstr "" - -msgid "settings.include_characters_in_translations" -msgstr "" - -msgid "settings.include_notes_in_translations" -msgstr "" - -msgid "settings.check_for_updates" -msgstr "" - msgid "n_of_n" msgstr "" @@ -374,6 +296,21 @@ msgstr "" msgid "errors.unexpected_assignment" msgstr "" +msgid "errors.expected_when_or_else" +msgstr "" + +msgid "errors.only_one_else_allowed" +msgstr "" + +msgid "errors.when_must_belong_to_match" +msgstr "" + +msgid "errors.concurrent_line_without_origin" +msgstr "" + +msgid "errors.goto_not_allowed_on_concurrect_lines" +msgstr "" + msgid "errors.unknown" msgstr "" @@ -468,4 +405,10 @@ msgid "runtime.unsupported_array_type" msgstr "" msgid "runtime.dialogue_balloon_missing_start_method" +msgstr "" + +msgid "runtime.top_level_states_share_name" +msgstr "" + +msgid "translation_plugin.character_name" msgstr "" \ No newline at end of file diff --git a/addons/dialogue_manager/l10n/uk.po b/addons/dialogue_manager/l10n/uk.po index e6404d7a..8cd41ac4 100644 --- a/addons/dialogue_manager/l10n/uk.po +++ b/addons/dialogue_manager/l10n/uk.po @@ -3,13 +3,13 @@ msgstr "" "Project-Id-Version: Dialogue Manager\n" "POT-Creation-Date: \n" "PO-Revision-Date: \n" -"Last-Translator: veydzh3r <veydzherdgswift008@gmail.com>\n" +"Last-Translator: \n" "Language-Team: \n" "Language: uk\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 3.5\n" +"X-Generator: Poedit 3.2.2\n" msgid "start_a_new_file" msgstr "Створити новий файл" @@ -36,7 +36,10 @@ msgid "find_in_files" msgstr "Знайти у файлах..." msgid "test_dialogue" -msgstr "Відтворити діалог" +msgstr "Протестувати діалог з початку файлу" + +msgid "test_dialogue_from_line" +msgstr "Протестувати діалог з поточного рядка" msgid "search_for_text" msgstr "Пошук тексту" @@ -47,9 +50,6 @@ msgstr "Вставити" msgid "translations" msgstr "Переклади" -msgid "settings" -msgstr "Налаштування" - msgid "sponsor" msgstr "Спонсор" @@ -143,97 +143,6 @@ msgstr "Копіювати шлях файлу" msgid "buffer.show_in_filesystem" msgstr "Показати у файловій системі" -msgid "settings.invalid_test_scene" -msgstr "«{path}» не у розширенні BaseDialogueTestScene." - -msgid "settings.revert_to_default_test_scene" -msgstr "Повернути до типової тестової сцени" - -msgid "settings.default_balloon_hint" -msgstr "" -"Нестандартна куля для використання під час виклику «DialogueManager." -"show_balloon()»" - -msgid "settings.revert_to_default_balloon" -msgstr "Повернути до типової кулі" - -msgid "settings.default_balloon_path" -msgstr "<приклад кулі>" - -msgid "settings.autoload" -msgstr "Автозавантаження" - -msgid "settings.path" -msgstr "Шлях" - -msgid "settings.new_template" -msgstr "Нові файли діалогів починатимуться з шаблонного тексту" - -msgid "settings.missing_keys" -msgstr "Вважати відсутні ключі перекладу як помилками" - -msgid "settings.missing_keys_hint" -msgstr "" -"Якщо ви використовуєте статичні ключі перекладу, увімкнення цього параметра " -"допоможе вам знайти рядки, до яких ви ще не додали ключ." - -msgid "settings.characters_translations" -msgstr "Експортовувати імена персонажів у файли перекладу" - -msgid "settings.wrap_long_lines" -msgstr "Переносити довгі рядки" - -msgid "settings.include_failed_responses" -msgstr "Включати відповіді з невдалими умовами" - -msgid "settings.ignore_missing_state_values" -msgstr "Пропускати помилки пропущених значень стану (не рекомендується)" - -msgid "settings.custom_test_scene" -msgstr "" -"Користувацька тестова сцена (має мати розширення «BaseDialogueTestScene»)" - -msgid "settings.default_csv_locale" -msgstr "Типова мова файлу CSV" - -msgid "settings.states_shortcuts" -msgstr "Скорочення станів" - -msgid "settings.states_message" -msgstr "" -"Якщо автозавантаження увімкнено, ви можете звертатися до його властивостей і " -"методів без необхідності використовувати його назву." - -msgid "settings.states_hint" -msgstr "" -"Тобто, замість «ЯкийсьСтан.якась_властивість» ви можете просто " -"використовувати «якась_властивість»" - -msgid "settings.recompile_warning" -msgstr "" -"Зміна цих параметрів призведе до перекомпіляції усіх діалогів. Змінюйте їх, " -"тільки якщо ви знаєте, що робите." - -msgid "settings.create_lines_for_responses_with_characters" -msgstr "Створювати дочірній рядок діалогу для відповідей з іменами персонажів" - -msgid "settings.open_in_external_editor" -msgstr "Відкрити файли діалогів у зовнішньому редакторі" - -msgid "settings.external_editor_warning" -msgstr "" -"Примітка: підсвічування синтаксису та детальна перевірка помилок не " -"підтримуються у зовнішніх редакторах." - -msgid "settings.include_characters_in_translations" -msgstr "Включати імена персонажів до експорту перекладу" - -msgid "settings.include_notes_in_translations" -msgstr "Включати примітки (## коментарі) до експорту перекладу" - -msgid "settings.check_for_updates" -msgstr "Перевіряти наявність оновлень" - msgid "n_of_n" msgstr "{index} з {total}" @@ -289,8 +198,7 @@ msgid "errors_in_script" msgstr "У вашому скрипті є помилки. Виправте їх і спробуйте ще раз." msgid "errors_with_build" -msgstr "" -"Вам потрібно виправити помилки в діалогах, перш ніж ви зможете запустити гру." +msgstr "Вам потрібно виправити помилки в діалогах, перш ніж ви зможете запустити гру." msgid "errors.import_errors" msgstr "В імпортованому файлі є помилки." @@ -397,6 +305,21 @@ msgstr "Недійсний індекс." msgid "errors.unexpected_assignment" msgstr "Несподіване призначення." +msgid "errors.expected_when_or_else" +msgstr "Очікувався випадок «when» або «else»." + +msgid "errors.only_one_else_allowed" +msgstr "Для кожного «match» допускається лише один випадок «else»." + +msgid "errors.when_must_belong_to_match" +msgstr "Оператори «when» можуть з’являтися лише як дочірні операторів «match»." + +msgid "errors.concurrent_line_without_origin" +msgstr "Паралельні рядки потребують початкового рядка, який не починається з «|»." + +msgid "errors.goto_not_allowed_on_concurrect_lines" +msgstr "У паралельних діалогових рядках не допускаються Goto посилання." + msgid "errors.unknown" msgstr "Невідомий синтаксис." @@ -446,9 +369,7 @@ msgid "runtime.error_detail" msgstr "Рядок {line}: {message}" msgid "runtime.errors_see_details" -msgstr "" -"У тексті діалогу було виявлено помилки ({count}). Див. детальніше у розділі " -"«Вивід»." +msgstr "У тексті діалогу було виявлено помилки ({count}). Див. детальніше у розділі «Вивід»." msgid "runtime.invalid_expression" msgstr "«{expression}» не є допустимим виразом: {error}" @@ -463,29 +384,16 @@ msgid "runtime.key_not_found" msgstr "Ключ «{key}» не знайдено у словнику «{dictionary}»" msgid "runtime.property_not_found" -msgstr "" -"«{property}» не знайдено. Стани з безпосередньо доступними властивостями/" -"методами/сигналами включають {states}. На автозавантаження потрібно " -"посилатися за їхніми назвами для використання їхніх властивостей." +msgstr "«{property}» не знайдено. Стани з безпосередньо доступними властивостями/методами/сигналами включають {states}. На автозавантаження потрібно посилатися за їхніми назвами для використання їхніх властивостей." msgid "runtime.property_not_found_missing_export" -msgstr "" -"«{property}» не знайдено. Можливо, вам слід додати декоратор «[Export]». " -"Стани з безпосередньо доступними властивостями/методами/сигналами включають " -"{states}. На автозавантаження потрібно посилатися за їхніми назвами для " -"використання їхніх властивостей." +msgstr "«{property}» не знайдено. Можливо, вам слід додати декоратор «[Export]». Стани з безпосередньо доступними властивостями/методами/сигналами включають {states}. На автозавантаження потрібно посилатися за їхніми назвами для використання їхніх властивостей." msgid "runtime.method_not_found" -msgstr "" -"Метод «{method}» не знайдено. Стани з безпосередньо доступними властивостями/" -"методами/сигналами включають {states}. На автозавантаження потрібно " -"посилатися за їхніми назвами для використання їхніх властивостей." +msgstr "Метод «{method}» не знайдено. Стани з безпосередньо доступними властивостями/методами/сигналами включають {states}. На автозавантаження потрібно посилатися за їхніми назвами для використання їхніх властивостей." msgid "runtime.signal_not_found" -msgstr "" -"Сигнал «{signal_name}» не знайдено. Стани з безпосередньо доступними " -"властивостями/методами/сигналами включають {states}. На автозавантаження " -"потрібно посилатися за їхніми назвами для використання їхніх властивостей." +msgstr "Сигнал «{signal_name}» не знайдено. Стани з безпосередньо доступними властивостями/методами/сигналами включають {states}. На автозавантаження потрібно посилатися за їхніми назвами для використання їхніх властивостей." msgid "runtime.method_not_callable" msgstr "«{method}» не є методом, який можна викликати в «{object}»" @@ -500,14 +408,16 @@ msgid "runtime.something_went_wrong" msgstr "Щось пішло не так." msgid "runtime.expected_n_got_n_args" -msgstr "" -"«{method}» було викликано з аргументами «{received}», але воно має лише " -"«{expected}»." +msgstr "«{method}» було викликано з аргументами «{received}», але воно має лише «{expected}»." msgid "runtime.unsupported_array_type" -msgstr "" -"Array[{type}] не підтримується у модифікаціях. Натомість використовуйте " -"Array як тип." +msgstr "Array[{type}] не підтримується у модифікаціях. Натомість використовуйте Array як тип." msgid "runtime.dialogue_balloon_missing_start_method" msgstr "У вашій кулі діалогу відсутній метод «start» або «Start»." + +msgid "runtime.top_level_states_share_name" +msgstr "Кілька станів верхнього рівня ({states}) мають спільну назву методу/властивості/сигналу «{key}». Для діалогу доступний лише перший випадок." + +msgid "translation_plugin.character_name" +msgstr "Ім’я персонажа" diff --git a/addons/dialogue_manager/l10n/zh.po b/addons/dialogue_manager/l10n/zh.po index e381944a..bafd1d58 100644 --- a/addons/dialogue_manager/l10n/zh.po +++ b/addons/dialogue_manager/l10n/zh.po @@ -44,9 +44,6 @@ msgstr "插入" msgid "translations" msgstr "翻译" -msgid "settings" -msgstr "设置" - msgid "show_support" msgstr "支持 Dialogue Manager" @@ -137,69 +134,6 @@ msgstr "复制文件路径" msgid "buffer.show_in_filesystem" msgstr "在 Godot 侧边栏中显示" -msgid "settings.revert_to_default_test_scene" -msgstr "重置测试场景设定" - -msgid "settings.default_balloon_hint" -msgstr "设置调用 \"DialogueManager.show_balloon()\" 时使用的对话框" - -msgid "settings.autoload" -msgstr "Autoload" - -msgid "settings.path" -msgstr "路径" - -msgid "settings.new_template" -msgstr "新建文件时自动插入模板" - -msgid "settings.missing_keys" -msgstr "将翻译键缺失视为错误" - -msgid "settings.missing_keys_hint" -msgstr "如果你使用静态键,这将会帮助你寻找未添加至翻译文件的键。" - -msgid "settings.characters_translations" -msgstr "在翻译文件中导出角色名" - -msgid "settings.wrap_long_lines" -msgstr "文本编辑器自动换行" - -msgid "settings.include_failed_responses" -msgstr "在判断条件失败时仍显示回复选项" - -msgid "settings.ignore_missing_state_values" -msgstr "忽略全局变量缺失错误(不建议)" - -msgid "settings.custom_test_scene" -msgstr "自定义测试场景(必须继承自BaseDialogueTestScene)" - -msgid "settings.default_csv_locale" -msgstr "默认 CSV 区域格式" - -msgid "settings.states_shortcuts" -msgstr "全局变量映射" - -msgid "settings.states_message" -msgstr "当一个 Autoload 在这里被勾选,他的所有成员会被映射为全局变量。" - -msgid "settings.states_hint" -msgstr "比如,当你开启对于“Foo”的映射时,你可以将“Foo.bar”简写成“bar”。" - -msgid "settings.recompile_warning" -msgstr "更改这些选项会强制重新编译所有的对话框,当你清楚在做什么的时候更改。" - -msgid "settings.create_lines_for_responses_with_characters" -msgstr "回复项带角色名时(- char: response),会自动生成为选择后的下一句对话" - -msgid "settings.include_characters_in_translations" -msgstr "导出 CSV 时包括角色名" - -msgid "settings.include_notes_in_translations" -msgstr "导出 CSV 时包括注释(## comments)" - -msgid "settings.check_for_updates" -msgstr "检查升级" - msgid "n_of_n" msgstr "第{index}个,共{total}个" diff --git a/addons/dialogue_manager/l10n/zh_TW.po b/addons/dialogue_manager/l10n/zh_TW.po index f4ce038a..e20feee8 100644 --- a/addons/dialogue_manager/l10n/zh_TW.po +++ b/addons/dialogue_manager/l10n/zh_TW.po @@ -44,9 +44,6 @@ msgstr "插入" msgid "translations" msgstr "翻譯" -msgid "settings" -msgstr "設定" - msgid "show_support" msgstr "支援 Dialogue Manager" @@ -137,69 +134,6 @@ msgstr "複製檔案位置" msgid "buffer.show_in_filesystem" msgstr "在 Godot 側邊欄中顯示" -msgid "settings.revert_to_default_test_scene" -msgstr "重置測試場景設定" - -msgid "settings.default_balloon_hint" -msgstr "設置使用 \"DialogueManager.show_balloon()\" 时的对话框" - -msgid "settings.autoload" -msgstr "Autoload" - -msgid "settings.path" -msgstr "路徑" - -msgid "settings.new_template" -msgstr "新建檔案時自動插入模板" - -msgid "settings.missing_keys" -msgstr "將翻譯鍵缺失視爲錯誤" - -msgid "settings.missing_keys_hint" -msgstr "如果你使用靜態鍵,這將會幫助你尋找未添加至翻譯檔案的鍵。" - -msgid "settings.wrap_long_lines" -msgstr "自動折行" - -msgid "settings.characters_translations" -msgstr "在翻譯檔案中匯出角色名。" - -msgid "settings.include_failed_responses" -msgstr "在判斷條件失敗時仍顯示回復選項" - -msgid "settings.ignore_missing_state_values" -msgstr "忽略全局變量缺失錯誤(不建議)" - -msgid "settings.custom_test_scene" -msgstr "自訂測試場景(必須繼承自BaseDialogueTestScene)" - -msgid "settings.default_csv_locale" -msgstr "預設 CSV 區域格式" - -msgid "settings.states_shortcuts" -msgstr "全局變量映射" - -msgid "settings.states_message" -msgstr "當一個 Autoload 在這裏被勾選,他的所有成員會被映射爲全局變量。" - -msgid "settings.states_hint" -msgstr "比如,當你開啓對於“Foo”的映射時,你可以將“Foo.bar”簡寫成“bar”。" - -msgid "settings.recompile_warning" -msgstr "更改這些選項會強制重新編譯所有的對話框,當你清楚在做什麼的時候更改。" - -msgid "settings.create_lines_for_responses_with_characters" -msgstr "回覆項目帶角色名稱時(- char: response),會自動產生為選擇後的下一句對話" - -msgid "settings.include_characters_in_translations" -msgstr "匯出 CSV 時包含角色名" - -msgid "settings.include_notes_in_translations" -msgstr "匯出 CSV 時包括註解(## comments)" - -msgid "settings.check_for_updates" -msgstr "檢查升級" - msgid "n_of_n" msgstr "第{index}個,共{total}個" diff --git a/addons/dialogue_manager/plugin.cfg b/addons/dialogue_manager/plugin.cfg index a6538d2b..9b558dbb 100644 --- a/addons/dialogue_manager/plugin.cfg +++ b/addons/dialogue_manager/plugin.cfg @@ -1,7 +1,7 @@ [plugin] name="Dialogue Manager" -description="A simple but powerful branching dialogue system" +description="A powerful nonlinear dialogue system" author="Nathan Hoad" -version="2.45.0" +version="3.4.0" script="plugin.gd" diff --git a/addons/dialogue_manager/plugin.cfg.uid b/addons/dialogue_manager/plugin.cfg.uid new file mode 100644 index 00000000..312d0cf0 --- /dev/null +++ b/addons/dialogue_manager/plugin.cfg.uid @@ -0,0 +1 @@ +uid://hrny2utekhei diff --git a/addons/dialogue_manager/plugin.gd b/addons/dialogue_manager/plugin.gd index f89fad4d..992ea3d5 100644 --- a/addons/dialogue_manager/plugin.gd +++ b/addons/dialogue_manager/plugin.gd @@ -2,21 +2,14 @@ extends EditorPlugin -const DialogueConstants = preload("./constants.gd") -const DialogueImportPlugin = preload("./import_plugin.gd") -const DialogueInspectorPlugin = preload("./inspector_plugin.gd") -const DialogueTranslationParserPlugin = preload("./editor_translation_parser_plugin.gd") -const DialogueSettings = preload("./settings.gd") -const DialogueCache = preload("./components/dialogue_cache.gd") const MainView = preload("./views/main_view.tscn") -const DialogueResource = preload("./dialogue_resource.gd") -var import_plugin: DialogueImportPlugin -var inspector_plugin: DialogueInspectorPlugin -var translation_parser_plugin: DialogueTranslationParserPlugin +var import_plugin: DMImportPlugin +var inspector_plugin: DMInspectorPlugin +var translation_parser_plugin: DMTranslationParserPlugin var main_view -var dialogue_cache: DialogueCache +var dialogue_cache: DMCache func _enter_tree() -> void: @@ -25,63 +18,87 @@ func _enter_tree() -> void: if Engine.is_editor_hint(): Engine.set_meta("DialogueManagerPlugin", self) - DialogueSettings.prepare() + DMSettings.prepare() - dialogue_cache = DialogueCache.new() - Engine.set_meta("DialogueCache", dialogue_cache) + dialogue_cache = DMCache.new() + Engine.set_meta("DMCache", dialogue_cache) - import_plugin = DialogueImportPlugin.new() + import_plugin = DMImportPlugin.new() add_import_plugin(import_plugin) - inspector_plugin = DialogueInspectorPlugin.new() + inspector_plugin = DMInspectorPlugin.new() add_inspector_plugin(inspector_plugin) - translation_parser_plugin = DialogueTranslationParserPlugin.new() + translation_parser_plugin = DMTranslationParserPlugin.new() add_translation_parser_plugin(translation_parser_plugin) main_view = MainView.instantiate() - get_editor_interface().get_editor_main_screen().add_child(main_view) + EditorInterface.get_editor_main_screen().add_child(main_view) _make_visible(false) main_view.add_child(dialogue_cache) _update_localization() - get_editor_interface().get_file_system_dock().files_moved.connect(_on_files_moved) - get_editor_interface().get_file_system_dock().file_removed.connect(_on_file_removed) + EditorInterface.get_file_system_dock().files_moved.connect(_on_files_moved) + EditorInterface.get_file_system_dock().file_removed.connect(_on_file_removed) add_tool_menu_item("Create copy of dialogue example balloon...", _copy_dialogue_balloon) - # Make sure the current balloon has a UID unique from the example balloon's - var balloon_path: String = DialogueSettings.get_setting("balloon_path", "") + # Automatically swap the script on the example balloon depending on if dotnet is being used. + if not FileAccess.file_exists("res://tests/test_basic_dialogue.gd"): + var plugin_path: String = get_plugin_path() + var balloon_file_names: PackedStringArray = ["example_balloon.tscn", "small_example_balloon.tscn"] + for balloon_file_name: String in balloon_file_names: + var balloon_path: String = plugin_path + "/example_balloon/" + balloon_file_name + var balloon_content: String = FileAccess.get_file_as_string(balloon_path) + if "example_balloon.gd" in balloon_content and DMSettings.check_for_dotnet_solution(): + balloon_content = balloon_content \ + # Replace script path with the C# one + .replace("example_balloon.gd", "ExampleBalloon.cs") \ + # Replace script UID with the C# one + .replace(ResourceUID.id_to_text(ResourceLoader.get_resource_uid(plugin_path + "/example_balloon/example_balloon.gd")), ResourceUID.id_to_text(ResourceLoader.get_resource_uid(plugin_path + "/example_balloon/ExampleBalloon.cs"))) + var balloon_file: FileAccess = FileAccess.open(balloon_path, FileAccess.WRITE) + balloon_file.store_string(balloon_content) + balloon_file.close() + elif "ExampleBalloon.cs" in balloon_content and not DMSettings.check_for_dotnet_solution(): + balloon_content = balloon_content \ + # Replace script path with the GDScript one + .replace("ExampleBalloon.cs", "example_balloon.gd") \ + # Replace script UID with the GDScript one + .replace(ResourceUID.id_to_text(ResourceLoader.get_resource_uid(plugin_path + "/example_balloon/ExampleBalloon.cs")), ResourceUID.id_to_text(ResourceLoader.get_resource_uid(plugin_path + "/example_balloon/example_balloon.gd"))) + var balloon_file: FileAccess = FileAccess.open(balloon_path, FileAccess.WRITE) + balloon_file.store_string(balloon_content) + balloon_file.close() + + # Automatically make any changes to the known custom balloon if there is one. + var balloon_path: String = DMSettings.get_setting(DMSettings.BALLOON_PATH, "") if balloon_path != "" and FileAccess.file_exists(balloon_path): var is_small_window: bool = ProjectSettings.get_setting("display/window/size/viewport_width") < 400 var example_balloon_file_name: String = "small_example_balloon.tscn" if is_small_window else "example_balloon.tscn" var example_balloon_path: String = get_plugin_path() + "/example_balloon/" + example_balloon_file_name + + var contents: String = FileAccess.get_file_as_string(balloon_path) + var has_changed: bool = false + + # Make sure the current balloon has a UID unique from the example balloon's var example_balloon_uid: String = ResourceUID.id_to_text(ResourceLoader.get_resource_uid(example_balloon_path)) var balloon_uid: String = ResourceUID.id_to_text(ResourceLoader.get_resource_uid(balloon_path)) if example_balloon_uid == balloon_uid: var new_balloon_uid: String = ResourceUID.id_to_text(ResourceUID.create_id()) - var contents: String = FileAccess.get_file_as_string(balloon_path) contents = contents.replace(example_balloon_uid, new_balloon_uid) + has_changed = true + + # Make sure the example balloon copy has the correct renaming of the responses menu + if "reponses" in contents: + contents = contents.replace("reponses", "responses") + has_changed = true + + # Save any changes + if has_changed: var balloon_file: FileAccess = FileAccess.open(balloon_path, FileAccess.WRITE) balloon_file.store_string(contents) balloon_file.close() - # Prevent the project from showing as unsaved even though it was only just opened - if DialogueSettings.get_setting("try_suppressing_startup_unsaved_indicator", false) \ - and Engine.get_physics_frames() == 0 \ - and get_editor_interface().has_method("save_all_scenes"): - var timer: Timer = Timer.new() - var suppress_unsaved_marker: Callable - suppress_unsaved_marker = func(): - if Engine.get_frames_per_second() >= 10: - timer.stop() - get_editor_interface().call("save_all_scenes") - timer.queue_free() - timer.timeout.connect(suppress_unsaved_marker) - add_child(timer) - timer.start(0.1) - func _exit_tree() -> void: remove_autoload_singleton("DialogueManager") @@ -99,10 +116,10 @@ func _exit_tree() -> void: main_view.queue_free() Engine.remove_meta("DialogueManagerPlugin") - Engine.remove_meta("DialogueCache") + Engine.remove_meta("DMCache") - get_editor_interface().get_file_system_dock().files_moved.disconnect(_on_files_moved) - get_editor_interface().get_file_system_dock().file_removed.disconnect(_on_file_removed) + EditorInterface.get_file_system_dock().files_moved.disconnect(_on_files_moved) + EditorInterface.get_file_system_dock().file_removed.disconnect(_on_file_removed) remove_tool_menu_item("Create copy of dialogue example balloon...") @@ -125,10 +142,10 @@ func _get_plugin_icon() -> Texture2D: func _handles(object) -> bool: - var editor_settings: EditorSettings = get_editor_interface().get_editor_settings() + var editor_settings: EditorSettings = EditorInterface.get_editor_settings() var external_editor: String = editor_settings.get_setting("text_editor/external/exec_path") var use_external_editor: bool = editor_settings.get_setting("text_editor/external/use_external_editor") and external_editor != "" - if object is DialogueResource and use_external_editor and DialogueSettings.get_user_value("open_in_external_editor", false): + if object is DialogueResource and use_external_editor and DMSettings.get_user_value("open_in_external_editor", false): var project_path: String = ProjectSettings.globalize_path("res://") var file_path: String = ProjectSettings.globalize_path(object.resource_path) OS.create_process(external_editor, [project_path, file_path]) @@ -150,10 +167,10 @@ func _apply_changes() -> void: func _build() -> bool: # If this is the dotnet Godot then we need to check if the solution file exists - DialogueSettings.check_for_dotnet_solution() + DMSettings.check_for_dotnet_solution() # Ignore errors in other files if we are just running the test scene - if DialogueSettings.get_user_value("is_running_test_scene", true): return true + if DMSettings.get_user_value("is_running_test_scene", true): return true if dialogue_cache != null: dialogue_cache.reimport_files() @@ -162,7 +179,7 @@ func _build() -> bool: if files_with_errors.size() > 0: for dialogue_file in files_with_errors: push_error("You have %d error(s) in %s" % [dialogue_file.errors.size(), dialogue_file.path]) - get_editor_interface().edit_resource(load(files_with_errors[0].path)) + EditorInterface.edit_resource(load(files_with_errors[0].path)) main_view.show_build_error_dialog() return false @@ -209,7 +226,7 @@ func get_editor_shortcuts() -> Dictionary: ] } - var paths = get_editor_interface().get_editor_paths() + var paths = EditorInterface.get_editor_paths() var settings if FileAccess.file_exists(paths.get_config_dir() + "/editor_settings-4.3.tres"): settings = load(paths.get_config_dir() + "/editor_settings-4.3.tres") @@ -283,7 +300,7 @@ func update_import_paths(from_path: String, to_path: String) -> void: # 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 + main_view.open_buffers[main_view.current_file_path].pristine_text = main_view.code_edit.text # Open the file and update the path var file: FileAccess = FileAccess.open(dependent.path, FileAccess.READ) @@ -323,7 +340,7 @@ func _update_localization() -> void: func _copy_dialogue_balloon() -> void: - var scale: float = get_editor_interface().get_editor_scale() + var scale: float = EditorInterface.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." @@ -332,8 +349,8 @@ func _copy_dialogue_balloon() -> void: directory_dialog.min_size = Vector2(600, 500) * scale directory_dialog.dir_selected.connect(func(path): var plugin_path: String = get_plugin_path() + var is_dotnet: bool = DMSettings.check_for_dotnet_solution() - var is_dotnet: bool = DialogueSettings.check_for_dotnet_solution() var balloon_path: String = path + ("/Balloon.tscn" if is_dotnet else "/balloon.tscn") var balloon_script_path: String = path + ("/DialogueBalloon.cs" if is_dotnet else "/balloon.gd") @@ -342,19 +359,12 @@ func _copy_dialogue_balloon() -> void: var example_balloon_file_name: String = "small_example_balloon.tscn" if is_small_window else "example_balloon.tscn" var example_balloon_path: String = plugin_path + "/example_balloon/" + example_balloon_file_name var example_balloon_script_file_name: String = "ExampleBalloon.cs" if is_dotnet else "example_balloon.gd" - var file_contents: String = FileAccess.get_file_as_string(example_balloon_path).replace(plugin_path + "/example_balloon/example_balloon.gd", balloon_script_path) - # Give the balloon a unique UID + var example_balloon_script_uid: String = ResourceUID.id_to_text(ResourceLoader.get_resource_uid(plugin_path + "/example_balloon/example_balloon.gd")) var example_balloon_uid: String = ResourceUID.id_to_text(ResourceLoader.get_resource_uid(example_balloon_path)) - var new_balloon_uid: String = ResourceUID.id_to_text(ResourceUID.create_id()) - file_contents = file_contents.replace(example_balloon_uid, new_balloon_uid) - # Save the new balloon - var file: FileAccess = FileAccess.open(balloon_path, FileAccess.WRITE) - file.store_string(file_contents) - file.close() # Copy the script file - file = FileAccess.open(plugin_path + "/example_balloon/" + example_balloon_script_file_name, FileAccess.READ) - file_contents = file.get_as_text() + var file: FileAccess = FileAccess.open(plugin_path + "/example_balloon/" + example_balloon_script_file_name, FileAccess.READ) + var file_contents: String = file.get_as_text() if is_dotnet: file_contents = file_contents.replace("class ExampleBalloon", "class DialogueBalloon") else: @@ -362,15 +372,30 @@ func _copy_dialogue_balloon() -> void: file = FileAccess.open(balloon_script_path, FileAccess.WRITE) file.store_string(file_contents) file.close() + var new_balloon_script_uid_raw: int = ResourceUID.create_id() + ResourceUID.add_id(new_balloon_script_uid_raw, balloon_script_path) + var new_balloon_script_uid: String = ResourceUID.id_to_text(new_balloon_script_uid_raw) - get_editor_interface().get_resource_filesystem().scan() - get_editor_interface().get_file_system_dock().call_deferred("navigate_to_path", balloon_path) + # Save the new balloon + file_contents = FileAccess.get_file_as_string(example_balloon_path) + if "example_balloon.gd" in file_contents: + file_contents = file_contents.replace(plugin_path + "/example_balloon/example_balloon.gd", balloon_script_path) + else: + file_contents = file_contents.replace(plugin_path + "/example_balloon/ExampleBalloon.cs", balloon_script_path) + var new_balloon_uid: String = ResourceUID.id_to_text(ResourceUID.create_id()) + file_contents = file_contents.replace(example_balloon_uid, new_balloon_uid).replace(example_balloon_script_uid, new_balloon_script_uid) + file = FileAccess.open(balloon_path, FileAccess.WRITE) + file.store_string(file_contents) + file.close() - DialogueSettings.set_setting("balloon_path", balloon_path) + EditorInterface.get_resource_filesystem().scan() + EditorInterface.get_file_system_dock().call_deferred("navigate_to_path", balloon_path) + + DMSettings.set_setting(DMSettings.BALLOON_PATH, balloon_path) directory_dialog.queue_free() ) - get_editor_interface().get_base_control().add_child(directory_dialog) + EditorInterface.get_base_control().add_child(directory_dialog) directory_dialog.popup_centered() @@ -379,7 +404,7 @@ func _copy_dialogue_balloon() -> void: 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) + DMSettings.move_recent_file(old_file, new_file) func _on_file_removed(file: String) -> void: diff --git a/addons/dialogue_manager/plugin.gd.uid b/addons/dialogue_manager/plugin.gd.uid new file mode 100644 index 00000000..40573b06 --- /dev/null +++ b/addons/dialogue_manager/plugin.gd.uid @@ -0,0 +1 @@ +uid://bpv426rpvrafa diff --git a/addons/dialogue_manager/settings.gd b/addons/dialogue_manager/settings.gd index 9554b644..0a0c12f2 100644 --- a/addons/dialogue_manager/settings.gd +++ b/addons/dialogue_manager/settings.gd @@ -1,91 +1,199 @@ @tool -extends Node +class_name DMSettings extends Node -const DialogueConstants = preload("./constants.gd") +#region Editor -### Editor config -const DEFAULT_SETTINGS = { - states = [], - missing_translations_are_errors = false, - export_characters_in_translation = true, - wrap_lines = false, - new_with_template = true, - new_template = "~ this_is_a_node_title\nNathan: [[Hi|Hello|Howdy]], this is some dialogue.\nNathan: Here are some choices.\n- First one\n\tNathan: You picked the first one.\n- Second one\n\tNathan: You picked the second one.\n- Start again => this_is_a_node_title\n- End the conversation => END\nNathan: For more information see the online documentation.\n=> END", - include_all_responses = false, - ignore_missing_state_values = false, - custom_test_scene_path = preload("./test_scene.tscn").resource_path, - default_csv_locale = "en", - balloon_path = "", - create_lines_for_responses_with_characters = true, - include_character_in_translation_exports = false, - include_notes_in_translation_exports = false, - uses_dotnet = false, - try_suppressing_startup_unsaved_indicator = false +## Wrap lines in the dialogue editor. +const WRAP_LONG_LINES = "editor/wrap_long_lines" +## The template to start new dialogue files with. +const NEW_FILE_TEMPLATE = "editor/new_file_template" + +## Show lines without statis IDs as errors. +const MISSING_TRANSLATIONS_ARE_ERRORS = "editor/translations/missing_translations_are_errors" +## Include character names in the list of translatable strings. +const INCLUDE_CHARACTERS_IN_TRANSLATABLE_STRINGS_LIST = "editor/translations/include_characters_in_translatable_strings_list" +## The default locale to use when exporting CSVs +const DEFAULT_CSV_LOCALE = "editor/translations/default_csv_locale" +## Any extra CSV locales to append to the exported translation CSV +const EXTRA_CSV_LOCALES = "editor/translations/extra_csv_locales" +## Includes a "_character" column in CSV exports. +const INCLUDE_CHARACTER_IN_TRANSLATION_EXPORTS = "editor/translations/include_character_in_translation_exports" +## Includes a "_notes" column in CSV exports +const INCLUDE_NOTES_IN_TRANSLATION_EXPORTS = "editor/translations/include_notes_in_translation_exports" + +## A custom test scene to use when testing dialogue. +const CUSTOM_TEST_SCENE_PATH = "editor/advanced/custom_test_scene_path" + +## The custom balloon for this game. +const BALLOON_PATH = "runtime/balloon_path" +## The names of any autoloads to shortcut into all dialogue files (so you don't have to write `using SomeGlobal` in each file). +const STATE_AUTOLOAD_SHORTCUTS = "runtime/state_autoload_shortcuts" +## Check for possible naming conflicts in state shortcuts. +const WARN_ABOUT_METHOD_PROPERTY_OR_SIGNAL_NAME_CONFLICTS = "runtime/warn_about_method_property_or_signal_name_conflicts" + +## Bypass any missing state when running dialogue. +const IGNORE_MISSING_STATE_VALUES = "runtime/advanced/ignore_missing_state_values" +## Whether or not the project is utilising dotnet. +const USES_DOTNET = "runtime/advanced/uses_dotnet" + + +const SETTINGS_CONFIGURATION = { + WRAP_LONG_LINES: { + value = false, + type = TYPE_BOOL, + }, + NEW_FILE_TEMPLATE: { + value = "~ start\nNathan: [[Hi|Hello|Howdy]], this is some dialogue.\nNathan: Here are some choices.\n- First one\n\tNathan: You picked the first one.\n- Second one\n\tNathan: You picked the second one.\n- Start again => start\n- End the conversation => END\nNathan: For more information see the online documentation.\n=> END", + type = TYPE_STRING, + hint = PROPERTY_HINT_MULTILINE_TEXT, + }, + + MISSING_TRANSLATIONS_ARE_ERRORS: { + value = false, + type = TYPE_BOOL, + is_advanced = true + }, + INCLUDE_CHARACTERS_IN_TRANSLATABLE_STRINGS_LIST: { + value = true, + type = TYPE_BOOL, + }, + DEFAULT_CSV_LOCALE: { + value = "en", + type = TYPE_STRING, + hint = PROPERTY_HINT_LOCALE_ID, + }, + EXTRA_CSV_LOCALES: { + value = [], + type = TYPE_PACKED_STRING_ARRAY, + is_advanced = true + }, + INCLUDE_CHARACTER_IN_TRANSLATION_EXPORTS: { + value = false, + type = TYPE_BOOL, + is_advanced = true + }, + INCLUDE_NOTES_IN_TRANSLATION_EXPORTS: { + value = false, + type = TYPE_BOOL, + is_advanced = true + }, + + CUSTOM_TEST_SCENE_PATH: { + value = preload("./test_scene.tscn").resource_path, + type = TYPE_STRING, + hint = PROPERTY_HINT_FILE, + is_advanced = true + }, + + BALLOON_PATH: { + value = "", + type = TYPE_STRING, + hint = PROPERTY_HINT_FILE, + }, + STATE_AUTOLOAD_SHORTCUTS: { + value = [], + type = TYPE_PACKED_STRING_ARRAY, + }, + WARN_ABOUT_METHOD_PROPERTY_OR_SIGNAL_NAME_CONFLICTS: { + value = false, + type = TYPE_BOOL, + is_advanced = true + }, + + IGNORE_MISSING_STATE_VALUES: { + value = false, + type = TYPE_BOOL, + is_advanced = true + }, + USES_DOTNET: { + value = false, + type = TYPE_BOOL, + is_hidden = true + } } static func prepare() -> void: - # Migrate previous keys - for key in [ - "states", - "missing_translations_are_errors", - "export_characters_in_translation", - "wrap_lines", - "new_with_template", - "include_all_responses", - "custom_test_scene_path" - ]: - if ProjectSettings.has_setting("dialogue_manager/%s" % key): - var value = ProjectSettings.get_setting("dialogue_manager/%s" % key) - ProjectSettings.set_setting("dialogue_manager/%s" % key, null) - set_setting(key, value) + var should_save_settings: bool = false + + # Remap any old settings into their new keys + var legacy_map: Dictionary = { + states = STATE_AUTOLOAD_SHORTCUTS, + missing_translations_are_errors = MISSING_TRANSLATIONS_ARE_ERRORS, + export_characters_in_translation = INCLUDE_CHARACTERS_IN_TRANSLATABLE_STRINGS_LIST, + wrap_lines = WRAP_LONG_LINES, + new_with_template = null, + new_template = NEW_FILE_TEMPLATE, + include_all_responses = null, + ignore_missing_state_values = IGNORE_MISSING_STATE_VALUES, + custom_test_scene_path = CUSTOM_TEST_SCENE_PATH, + default_csv_locale = DEFAULT_CSV_LOCALE, + balloon_path = BALLOON_PATH, + create_lines_for_responses_with_characters = null, + include_character_in_translation_exports = INCLUDE_CHARACTER_IN_TRANSLATION_EXPORTS, + include_notes_in_translation_exports = INCLUDE_NOTES_IN_TRANSLATION_EXPORTS, + uses_dotnet = USES_DOTNET, + try_suppressing_startup_unsaved_indicator = null + } + + for legacy_key: String in legacy_map: + if ProjectSettings.has_setting("dialogue_manager/general/%s" % legacy_key): + should_save_settings = true + # Remove the old setting + var value = ProjectSettings.get_setting("dialogue_manager/general/%s" % legacy_key) + ProjectSettings.set_setting("dialogue_manager/general/%s" % legacy_key, null) + if legacy_map.get(legacy_key) != null: + prints("Migrating Dialogue Manager setting %s to %s with value %s" % [legacy_key, legacy_map.get(legacy_key), str(value)]) + ProjectSettings.set_setting("dialogue_manager/%s" % [legacy_map.get(legacy_key)], value) # Set up initial settings - for setting in DEFAULT_SETTINGS: - var setting_name: String = "dialogue_manager/general/%s" % setting + for key: String in SETTINGS_CONFIGURATION: + var setting_config: Dictionary = SETTINGS_CONFIGURATION[key] + var setting_name: String = "dialogue_manager/%s" % key if not ProjectSettings.has_setting(setting_name): - set_setting(setting, DEFAULT_SETTINGS[setting]) - ProjectSettings.set_initial_value(setting_name, DEFAULT_SETTINGS[setting]) - if setting.ends_with("_path"): - ProjectSettings.add_property_info({ - "name": setting_name, - "type": TYPE_STRING, - "hint": PROPERTY_HINT_FILE, - }) + ProjectSettings.set_setting(setting_name, setting_config.value) + ProjectSettings.set_initial_value(setting_name, setting_config.value) + ProjectSettings.add_property_info({ + "name" = setting_name, + "type" = setting_config.type, + "hint" = setting_config.get("hint", PROPERTY_HINT_NONE), + "hint_string" = setting_config.get("hint_string", "") + }) + ProjectSettings.set_as_basic(setting_name, not setting_config.has("is_advanced")) + ProjectSettings.set_as_internal(setting_name, setting_config.has("is_hidden")) - # Some settings shouldn't be edited directly in the Project Settings window - ProjectSettings.set_as_internal("dialogue_manager/general/states", true) - ProjectSettings.set_as_internal("dialogue_manager/general/custom_test_scene_path", true) - ProjectSettings.set_as_internal("dialogue_manager/general/uses_dotnet", true) - - ProjectSettings.save() + if should_save_settings: + 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() + if get_setting(key, value) != value: + ProjectSettings.set_setting("dialogue_manager/%s" % key, value) + ProjectSettings.set_initial_value("dialogue_manager/%s" % key, SETTINGS_CONFIGURATION[key].value) + 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) + if ProjectSettings.has_setting("dialogue_manager/%s" % key): + return ProjectSettings.get_setting("dialogue_manager/%s" % key) else: return default static func get_settings(only_keys: PackedStringArray = []) -> Dictionary: var settings: Dictionary = {} - for key in DEFAULT_SETTINGS.keys(): + for key in SETTINGS_CONFIGURATION.keys(): if only_keys.is_empty() or key in only_keys: - settings[key] = get_setting(key, DEFAULT_SETTINGS[key]) + settings[key] = get_setting(key, SETTINGS_CONFIGURATION[key].value) return settings -### User config +#endregion + +#region User static func get_user_config() -> Dictionary: @@ -103,15 +211,15 @@ static func get_user_config() -> Dictionary: open_in_external_editor = false } - if FileAccess.file_exists(DialogueConstants.USER_CONFIG_PATH): - var file: FileAccess = FileAccess.open(DialogueConstants.USER_CONFIG_PATH, FileAccess.READ) + if FileAccess.file_exists(DMConstants.USER_CONFIG_PATH): + var file: FileAccess = FileAccess.open(DMConstants.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) + var file: FileAccess = FileAccess.open(DMConstants.USER_CONFIG_PATH, FileAccess.WRITE) file.store_string(JSON.stringify(user_config)) @@ -182,7 +290,10 @@ static func check_for_dotnet_solution() -> bool: var directory: String = ProjectSettings.get("dotnet/project/solution_directory") var file_name: String = ProjectSettings.get("dotnet/project/assembly_name") has_dotnet_solution = FileAccess.file_exists("res://%s/%s.sln" % [directory, file_name]) - set_setting("uses_dotnet", has_dotnet_solution) + set_setting(DMSettings.USES_DOTNET, has_dotnet_solution) return has_dotnet_solution - return get_setting("uses_dotnet", false) + return get_setting(DMSettings.USES_DOTNET, false) + + +#endregion diff --git a/addons/dialogue_manager/settings.gd.uid b/addons/dialogue_manager/settings.gd.uid new file mode 100644 index 00000000..c93da98d --- /dev/null +++ b/addons/dialogue_manager/settings.gd.uid @@ -0,0 +1 @@ +uid://ce1nk88365m52 diff --git a/addons/dialogue_manager/test_scene.gd b/addons/dialogue_manager/test_scene.gd index a8089114..20fe115f 100644 --- a/addons/dialogue_manager/test_scene.gd +++ b/addons/dialogue_manager/test_scene.gd @@ -10,23 +10,34 @@ const DialogueResource = preload("./dialogue_resource.gd") 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) + # Is this running in Godot >=4.4? + if Engine.has_method("is_embedded_in_editor"): + if not Engine.call("is_embedded_in_editor"): + var window: Window = get_viewport() + var screen_index: int = DisplayServer.get_primary_screen() + window.position = Vector2(DisplayServer.screen_get_position(screen_index)) + (DisplayServer.screen_get_size(screen_index) - window.size) * 0.5 + window.mode = Window.MODE_WINDOWED + else: + var screen_index: int = DisplayServer.get_primary_screen() + DisplayServer.window_set_position(Vector2(DisplayServer.screen_get_position(screen_index)) + (DisplayServer.screen_get_size(screen_index) - DisplayServer.window_get_size()) * 0.5) + DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED) # Normally you can just call DialogueManager directly but doing so before the plugin has been # enabled in settings will throw a compiler error here so I'm using `get_singleton` instead. var dialogue_manager = Engine.get_singleton("DialogueManager") dialogue_manager.dialogue_ended.connect(_on_dialogue_ended) - dialogue_manager.show_dialogue_balloon(resource, title) + dialogue_manager.show_dialogue_balloon(resource, title if not title.is_empty() else resource.first_title) func _enter_tree() -> void: DialogueSettings.set_user_value("is_running_test_scene", false) -### Signals +#region Signals func _on_dialogue_ended(_resource: DialogueResource): get_tree().quit() + + +#endregion diff --git a/addons/dialogue_manager/test_scene.gd.uid b/addons/dialogue_manager/test_scene.gd.uid new file mode 100644 index 00000000..1bee7a14 --- /dev/null +++ b/addons/dialogue_manager/test_scene.gd.uid @@ -0,0 +1 @@ +uid://c8e16qdgu40wo diff --git a/addons/dialogue_manager/test_scene.tscn b/addons/dialogue_manager/test_scene.tscn index f2bbd8d3..f0786bad 100644 --- a/addons/dialogue_manager/test_scene.tscn +++ b/addons/dialogue_manager/test_scene.tscn @@ -1,7 +1,6 @@ -[gd_scene load_steps=2 format=3] - -[ext_resource type="Script" path="res://addons/dialogue_manager/test_scene.gd" id="1_yupoh"] +[gd_scene load_steps=2 format=3 uid="uid://ugd552efvil0"] +[ext_resource type="Script" uid="uid://c8e16qdgu40wo" path="res://addons/dialogue_manager/test_scene.gd" id="1_yupoh"] [node name="TestScene" type="Node2D"] script = ExtResource("1_yupoh") diff --git a/addons/dialogue_manager/utilities/builtins.gd b/addons/dialogue_manager/utilities/builtins.gd index 97080b70..1f8f8ba3 100644 --- a/addons/dialogue_manager/utilities/builtins.gd +++ b/addons/dialogue_manager/utilities/builtins.gd @@ -22,8 +22,32 @@ const SUPPORTED_BUILTIN_TYPES = [ static var resolve_method_error: Error = OK -static func is_supported(thing) -> bool: - return typeof(thing) in SUPPORTED_BUILTIN_TYPES +static func is_supported(thing, with_method: String = "") -> bool: + if not typeof(thing) in SUPPORTED_BUILTIN_TYPES: return false + + # If given a Dictionary and a method then make sure it's a known Dictionary method. + if typeof(thing) == TYPE_DICTIONARY and with_method != "": + return with_method in [ + &"clear", + &"duplicate", + &"erase", + &"find_key", + &"get", + &"get_or_add", + &"has", + &"has_all", + &"hash", + &"is_empty", + &"is_read_only", + &"keys", + &"make_read_only", + &"merge", + &"merged", + &"recursive_equal", + &"size", + &"values"] + + return true static func resolve_property(builtin, property: String): @@ -407,6 +431,15 @@ static func resolve_vector2_property(vector: Vector2, property: String): "DOWN": return Vector2.DOWN + "DOWN_LEFT": + return Vector2(-1, 1) + "DOWN_RIGHT": + return Vector2(1, 1) + "UP_LEFT": + return Vector2(-1, -1) + "UP_RIGHT": + return Vector2(1, -1) + return vector[property] diff --git a/addons/dialogue_manager/utilities/builtins.gd.uid b/addons/dialogue_manager/utilities/builtins.gd.uid new file mode 100644 index 00000000..af8698cd --- /dev/null +++ b/addons/dialogue_manager/utilities/builtins.gd.uid @@ -0,0 +1 @@ +uid://bnfhuubdv5k20 diff --git a/addons/dialogue_manager/components/dialogue_cache.gd b/addons/dialogue_manager/utilities/dialogue_cache.gd similarity index 84% rename from addons/dialogue_manager/components/dialogue_cache.gd rename to addons/dialogue_manager/utilities/dialogue_cache.gd index 5aa9a573..dd1da441 100644 --- a/addons/dialogue_manager/components/dialogue_cache.gd +++ b/addons/dialogue_manager/utilities/dialogue_cache.gd @@ -1,9 +1,4 @@ -extends Node - - -const DialogueConstants = preload("../constants.gd") -const DialogueSettings = preload("../settings.gd") -const DialogueManagerParseResult = preload("./parse_result.gd") +class_name DMCache extends Node signal file_content_changed(path: String, new_content: String) @@ -45,27 +40,23 @@ func reimport_files(and_files: PackedStringArray = []) -> void: if _files_marked_for_reimport.is_empty(): return - var file_system: EditorFileSystem = Engine.get_meta("DialogueManagerPlugin") \ - .get_editor_interface() \ - .get_resource_filesystem() - - file_system.reimport_files(_files_marked_for_reimport) + EditorInterface.get_resource_filesystem().reimport_files(_files_marked_for_reimport) ## Add a dialogue file to the cache. -func add_file(path: String, parse_results: DialogueManagerParseResult = null) -> void: +func add_file(path: String, compile_result: DMCompilerResult = null) -> void: _cache[path] = { path = path, dependencies = [], errors = [] } - if parse_results != null: - _cache[path].dependencies = Array(parse_results.imported_paths).filter(func(d): return d != path) - _cache[path].parsed_at = Time.get_ticks_msec() + if compile_result != null: + _cache[path].dependencies = Array(compile_result.imported_paths).filter(func(d): return d != path) + _cache[path].compiled_at = Time.get_ticks_msec() # If this is a fresh cache entry, check for dependencies - if parse_results == null and not _update_dependency_paths.has(path): + if compile_result == null and not _update_dependency_paths.has(path): queue_updating_dependencies(path) @@ -126,7 +117,7 @@ func get_files_with_dependency(imported_path: String) -> Array: ## Get any paths that are dependent on a given path func get_dependent_paths_for_reimport(on_path: String) -> PackedStringArray: return get_files_with_dependency(on_path) \ - .filter(func(d): return Time.get_ticks_msec() - d.get("parsed_at", 0) > 3000) \ + .filter(func(d): return Time.get_ticks_msec() - d.get("compiled_at", 0) > 3000) \ .map(func(d): return d.path) @@ -157,7 +148,7 @@ func _get_dialogue_files_in_filesystem(path: String = "res://") -> PackedStringA return files -### Signals +#region Signals func _on_update_dependency_timeout() -> void: @@ -174,3 +165,6 @@ func _on_update_dependency_timeout() -> void: dependencies.append(found.strings[found.names.path]) _cache[path].dependencies = dependencies _update_dependency_paths.clear() + + +#endregion diff --git a/addons/dialogue_manager/utilities/dialogue_cache.gd.uid b/addons/dialogue_manager/utilities/dialogue_cache.gd.uid new file mode 100644 index 00000000..e572006a --- /dev/null +++ b/addons/dialogue_manager/utilities/dialogue_cache.gd.uid @@ -0,0 +1 @@ +uid://d3c83yd6bjp43 diff --git a/addons/dialogue_manager/views/main_view.gd b/addons/dialogue_manager/views/main_view.gd index 15486022..00581dcd 100644 --- a/addons/dialogue_manager/views/main_view.gd +++ b/addons/dialogue_manager/views/main_view.gd @@ -2,11 +2,6 @@ extends Control -const DialogueConstants = preload("../constants.gd") -const DialogueSettings = preload("../settings.gd") -const DialogueResource = preload("../dialogue_resource.gd") -const DialogueManagerParser = preload("../components/parser.gd") - const OPEN_OPEN = 100 const OPEN_QUICK = 101 const OPEN_CLEAR = 102 @@ -33,7 +28,7 @@ enum TranslationSource { signal confirmation_closed() -@onready var parse_timer := $ParseTimer +@onready var parse_timer: Timer = $ParseTimer # Dialogs @onready var new_dialog: FileDialog = $NewDialog @@ -44,8 +39,6 @@ signal confirmation_closed() @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 @@ -58,10 +51,10 @@ signal confirmation_closed() @onready var save_all_button: Button = %SaveAllButton @onready var find_in_files_button: Button = %FindInFilesButton @onready var test_button: Button = %TestButton +@onready var test_line_button: Button = %TestLineButton @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 support_button: Button = %SupportButton @onready var docs_button: Button = %DocsButton @onready var version_label: Label = %VersionLabel @@ -74,7 +67,7 @@ signal confirmation_closed() @onready var files_list := %FilesList @onready var files_popup_menu: PopupMenu = %FilesPopupMenu @onready var title_list := %TitleList -@onready var code_edit := %CodeEdit +@onready var code_edit: DMCodeEdit = %CodeEdit @onready var errors_panel := %ErrorsPanel # The currently open file @@ -85,6 +78,7 @@ var current_file_path: String = "": if current_file_path == "" or not open_buffers.has(current_file_path): save_all_button.disabled = true test_button.disabled = true + test_line_button.disabled = true search_button.disabled = true insert_button.disabled = true translations_button.disabled = true @@ -95,6 +89,7 @@ var current_file_path: String = "": errors_panel.hide() else: test_button.disabled = false + test_line_button.disabled = false search_button.disabled = false insert_button.disabled = false translations_button.disabled = false @@ -106,7 +101,7 @@ var current_file_path: String = "": 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.set_cursor(DMSettings.get_caret(current_file_path)) code_edit.grab_focus() _on_code_edit_text_changed() @@ -137,16 +132,16 @@ func _ready() -> void: version_label.text = "v%s" % plugin.get_version() update_button.on_before_refresh = func on_before_refresh(): # Save everything - DialogueSettings.set_user_value("just_refreshed", { + DMSettings.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) + var just_refreshed = DMSettings.get_user_value("just_refreshed", null) if just_refreshed != null: - DialogueSettings.set_user_value("just_refreshed", null) + DMSettings.set_user_value("just_refreshed", null) call_deferred("load_from_version_refresh", just_refreshed) # Hook up the search toolbar @@ -157,35 +152,35 @@ func _ready() -> void: 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 = plugin.get_editor_interface().get_editor_settings() + code_edit.wrap_mode = TextEdit.LINE_WRAPPING_BOUNDARY if DMSettings.get_setting(DMSettings.WRAP_LONG_LINES, false) else TextEdit.LINE_WRAPPING_NONE + var editor_settings: EditorSettings = EditorInterface.get_editor_settings() editor_settings.settings_changed.connect(_on_editor_settings_changed) _on_editor_settings_changed() # Reopen any files that were open when Godot was closed if editor_settings.get_setting("text_editor/behavior/files/restore_scripts_on_load"): - var reopen_files: Array = DialogueSettings.get_user_value("reopen_files", []) + var reopen_files: Array = DMSettings.get_user_value("reopen_files", []) for reopen_file in reopen_files: open_file(reopen_file) - self.current_file_path = DialogueSettings.get_user_value("most_recent_reopen_file", "") + self.current_file_path = DMSettings.get_user_value("most_recent_reopen_file", "") 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") + close_confirmation_dialog.ok_button_text = DMConstants.translate(&"confirm_close.save") + close_confirmation_dialog.add_button(DMConstants.translate(&"confirm_close.discard"), true, "discard") - errors_dialog.dialog_text = DialogueConstants.translate(&"errors_in_script") + errors_dialog.dialog_text = DMConstants.translate(&"errors_in_script") # Update the buffer if a file was modified externally (retains undo step) - Engine.get_meta("DialogueCache").file_content_changed.connect(_on_cache_file_content_changed) + Engine.get_meta("DMCache").file_content_changed.connect(_on_cache_file_content_changed) - plugin.get_editor_interface().get_file_system_dock().files_moved.connect(_on_files_moved) + EditorInterface.get_file_system_dock().files_moved.connect(_on_files_moved) func _exit_tree() -> void: - DialogueSettings.set_user_value("reopen_files", open_buffers.keys()) - DialogueSettings.set_user_value("most_recent_reopen_file", self.current_file_path) + DMSettings.set_user_value("reopen_files", open_buffers.keys()) + DMSettings.set_user_value("most_recent_reopen_file", self.current_file_path) func _unhandled_input(event: InputEvent) -> void: @@ -225,13 +220,12 @@ func load_from_version_refresh(just_refreshed: Dictionary) -> void: else: open_buffers = just_refreshed.open_buffers - var interface: EditorInterface = plugin.get_editor_interface() if just_refreshed.current_file_path != "": - interface.edit_resource(load(just_refreshed.current_file_path)) + EditorInterface.edit_resource(load(just_refreshed.current_file_path)) else: - interface.set_main_screen_editor("Dialogue") + EditorInterface.set_main_screen_editor("Dialogue") - updated_dialog.dialog_text = DialogueConstants.translate(&"update.success").format({ version = update_button.get_version() }) + updated_dialog.dialog_text = DMConstants.translate(&"update.success").format({ version = update_button.get_version() }) updated_dialog.popup_centered() @@ -241,12 +235,11 @@ func new_file(path: String, content: String = "") -> void: var file: FileAccess = FileAccess.open(path, FileAccess.WRITE) if content == "": - if DialogueSettings.get_setting("new_with_template", true): - file.store_string(DialogueSettings.get_setting("new_template", "")) + file.store_string(DMSettings.get_setting(DMSettings.NEW_FILE_TEMPLATE, "")) else: file.store_string(content) - plugin.get_editor_interface().get_resource_filesystem().scan() + EditorInterface.get_resource_filesystem().scan() # Open a dialogue resource for editing @@ -267,7 +260,7 @@ func open_file(path: String) -> void: pristine_text = text } - DialogueSettings.add_recent_file(path) + DMSettings.add_recent_file(path) build_open_menu() files_list.files = open_buffers.keys() @@ -277,11 +270,7 @@ func open_file(path: String) -> void: func show_file_in_filesystem(path: String) -> void: - var file_system_dock: FileSystemDock = plugin \ - .get_editor_interface() \ - .get_file_system_dock() - - file_system_dock.navigate_to_path(path) + EditorInterface.get_file_system_dock().navigate_to_path(path) # Save any open files @@ -295,7 +284,7 @@ func save_files() -> void: save_file(path, false) if saved_files.size() > 0: - Engine.get_meta("DialogueCache").mark_files_for_reimport(saved_files) + Engine.get_meta("DMCache").mark_files_for_reimport(saved_files) # Save a file @@ -317,10 +306,7 @@ func save_file(path: String, rescan_file_system: bool = true) -> void: file.close() if rescan_file_system: - plugin \ - .get_editor_interface() \ - .get_resource_filesystem()\ - .scan() + EditorInterface.get_resource_filesystem().scan() func close_file(path: String) -> void: @@ -332,7 +318,7 @@ func close_file(path: String) -> void: remove_file_from_open_buffers(path) await get_tree().process_frame else: - close_confirmation_dialog.dialog_text = DialogueConstants.translate(&"confirm_close").format({ path = path.get_file() }) + close_confirmation_dialog.dialog_text = DMConstants.translate(&"confirm_close").format({ path = path.get_file() }) close_confirmation_dialog.popup_centered() await confirmation_closed @@ -355,8 +341,8 @@ func remove_file_from_open_buffers(path: String) -> void: # Apply theme colors and icons to the UI func apply_theme() -> void: if is_instance_valid(plugin) and is_instance_valid(code_edit): - var scale: float = plugin.get_editor_interface().get_editor_scale() - var editor_settings = plugin.get_editor_interface().get_editor_settings() + var scale: float = EditorInterface.get_editor_scale() + var editor_settings = EditorInterface.get_editor_settings() code_edit.theme_overrides = { scale = scale, @@ -382,68 +368,68 @@ func apply_theme() -> void: } new_button.icon = get_theme_icon("New", "EditorIcons") - new_button.tooltip_text = DialogueConstants.translate(&"start_a_new_file") + new_button.tooltip_text = DMConstants.translate(&"start_a_new_file") open_button.icon = get_theme_icon("Load", "EditorIcons") - open_button.tooltip_text = DialogueConstants.translate(&"open_a_file") + open_button.tooltip_text = DMConstants.translate(&"open_a_file") save_all_button.icon = get_theme_icon("Save", "EditorIcons") - save_all_button.tooltip_text = DialogueConstants.translate(&"start_all_files") + save_all_button.tooltip_text = DMConstants.translate(&"start_all_files") find_in_files_button.icon = get_theme_icon("ViewportZoom", "EditorIcons") - find_in_files_button.tooltip_text = DialogueConstants.translate(&"find_in_files") + find_in_files_button.tooltip_text = DMConstants.translate(&"find_in_files") - test_button.icon = get_theme_icon("PlayScene", "EditorIcons") - test_button.tooltip_text = DialogueConstants.translate(&"test_dialogue") + test_button.icon = get_theme_icon("DebugNext", "EditorIcons") + test_button.tooltip_text = DMConstants.translate(&"test_dialogue") + + test_line_button.icon = get_theme_icon("DebugStep", "EditorIcons") + test_line_button.tooltip_text = DMConstants.translate(&"test_dialogue_from_line") search_button.icon = get_theme_icon("Search", "EditorIcons") - search_button.tooltip_text = DialogueConstants.translate(&"search_for_text") + search_button.tooltip_text = DMConstants.translate(&"search_for_text") insert_button.icon = get_theme_icon("RichTextEffect", "EditorIcons") - insert_button.text = DialogueConstants.translate(&"insert") + insert_button.text = DMConstants.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") + translations_button.text = DMConstants.translate(&"translations") support_button.icon = get_theme_icon("Heart", "EditorIcons") - support_button.text = DialogueConstants.translate(&"sponsor") - support_button.tooltip_text = DialogueConstants.translate(&"show_support") + support_button.text = DMConstants.translate(&"sponsor") + support_button.tooltip_text = DMConstants.translate(&"show_support") docs_button.icon = get_theme_icon("Help", "EditorIcons") - docs_button.text = DialogueConstants.translate(&"docs") + docs_button.text = DMConstants.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_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DMConstants.translate(&"insert.wave_bbcode"), 0) + popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DMConstants.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) + popup.add_icon_item(get_theme_icon("Time", "EditorIcons"), DMConstants.translate(&"insert.typing_pause"), 3) + popup.add_icon_item(get_theme_icon("ViewportSpeed", "EditorIcons"), DMConstants.translate(&"insert.typing_speed_change"), 4) + popup.add_icon_item(get_theme_icon("DebugNext", "EditorIcons"), DMConstants.translate(&"insert.auto_advance"), 5) + popup.add_separator(DMConstants.translate(&"insert.templates")) + popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DMConstants.translate(&"insert.title"), 6) + popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DMConstants.translate(&"insert.dialogue"), 7) + popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DMConstants.translate(&"insert.response"), 8) + popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DMConstants.translate(&"insert.random_lines"), 9) + popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DMConstants.translate(&"insert.random_text"), 10) + popup.add_separator(DMConstants.translate(&"insert.actions")) + popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DMConstants.translate(&"insert.jump"), 11) + popup.add_icon_item(get_theme_icon("RichTextEffect", "EditorIcons"), DMConstants.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_icon_item(get_theme_icon("Translation", "EditorIcons"), DMConstants.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) + popup.add_icon_item(get_theme_icon("FileList", "EditorIcons"), DMConstants.translate(&"save_characters_to_csv"), TRANSLATIONS_SAVE_CHARACTERS_TO_CSV) + popup.add_icon_item(get_theme_icon("FileList", "EditorIcons"), DMConstants.translate(&"save_to_csv"), TRANSLATIONS_SAVE_TO_CSV) + popup.add_icon_item(get_theme_icon("AssetLib", "EditorIcons"), DMConstants.translate(&"import_from_csv"), TRANSLATIONS_IMPORT_FROM_CSV) # Dialog sizes new_dialog.min_size = Vector2(600, 500) * scale @@ -452,8 +438,6 @@ func apply_theme() -> void: quick_open_dialog.min_size = Vector2(400, 600) * scale export_dialog.min_size = Vector2(600, 500) * scale import_dialog.min_size = Vector2(600, 500) * scale - settings_dialog.min_size = Vector2(1000, 600) * scale - settings_dialog.max_size = Vector2(1000, 600) * scale find_in_files_dialog.min_size = Vector2(800, 600) * scale @@ -464,13 +448,13 @@ func apply_theme() -> void: 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_icon_item(get_theme_icon("Load", "EditorIcons"), DialogueConstants.translate(&"open.quick_open"), OPEN_QUICK) + menu.add_icon_item(get_theme_icon("Load", "EditorIcons"), DMConstants.translate(&"open.open"), OPEN_OPEN) + menu.add_icon_item(get_theme_icon("Load", "EditorIcons"), DMConstants.translate(&"open.quick_open"), OPEN_QUICK) menu.add_separator() - var recent_files = DialogueSettings.get_recent_files() + var recent_files = DMSettings.get_recent_files() if recent_files.size() == 0: - menu.add_item(DialogueConstants.translate(&"open.no_recent_files")) + menu.add_item(DMConstants.translate(&"open.no_recent_files")) menu.set_item_disabled(2, true) else: for path in recent_files: @@ -478,7 +462,7 @@ func build_open_menu() -> void: menu.add_icon_item(get_theme_icon("File", "EditorIcons"), path) menu.add_separator() - menu.add_item(DialogueConstants.translate(&"open.clear_recent_files"), OPEN_CLEAR) + menu.add_item(DMConstants.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) @@ -487,27 +471,22 @@ func build_open_menu() -> void: # 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 + return DMSettings.get_user_value("last_export_path", current_file_path.get_base_dir()) + "/" + filename # Check the current text for errors -func parse() -> void: +func compile() -> 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() - + var result: DMCompilerResult = DMCompiler.compile_string(code_edit.text, current_file_path) + code_edit.errors = result.errors + errors_panel.errors = result.errors title_list.titles = code_edit.get_titles() func show_build_error_dialog() -> void: - build_error_dialog.dialog_text = DialogueConstants.translate(&"errors_with_build") + build_error_dialog.dialog_text = DMConstants.translate(&"errors_with_build") build_error_dialog.popup_centered() @@ -516,8 +495,6 @@ 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") @@ -533,7 +510,7 @@ func generate_translations_keys() -> void: var text = "" var l = line.replace(found.strings[0], "").strip_edges().strip_edges() if l.begins_with("- "): - text = parser.extract_response_prompt(l) + text = DMCompiler.extract_translatable_string(l) elif ":" in l: text = l.split(":")[1] else: @@ -545,37 +522,43 @@ func generate_translations_keys() -> void: 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 not [DMConstants.TYPE_DIALOGUE, DMConstants.TYPE_RESPONSE].has(DMCompiler.get_line_type(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) + text = DMCompiler.extract_translatable_string(l) else: text = l.substr(l.find(":") + 1) - text = text.replace("\n", "!NEWLINE!") - line = line.replace("\\n", "!NEWLINE!") + var key: String = "" + if known_keys.values().has(text): + key = known_keys.find_key(text) + else: + var regex: DMCompilerRegEx = DMCompilerRegEx.new() + key = regex.ALPHA_NUMERIC.sub(text.strip_edges(), "_", true).substr(0, 30) + if key.begins_with("_"): + key = key.substr(1) + if key.ends_with("_"): + key = key.substr(0, key.length() - 1) + + # Make sure key is unique + var hashed_key: String = key + "_" + str(randi() % 1000000).sha1_text().substr(0, 6) + while hashed_key in known_keys and text != known_keys.get(hashed_key): + hashed_key = key + "_" + str(randi() % 1000000).sha1_text().substr(0, 6) + key = hashed_key.to_upper() + + line = line.replace("\\n", "!NEWLINE!") + text = text.replace("\n", "!NEWLINE!") + lines[i] = line.replace(text, text + " [ID:%s]" % [key]).replace("!NEWLINE!", "\\n") - lines[i] = line.replace(text, text + " [ID:%s]" % key).replace("!NEWLINE!", "\\n") 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: @@ -587,7 +570,7 @@ func add_path_to_project_translations(path: String) -> void: # Export dialogue and responses to CSV func export_translations_to_csv(path: String) -> void: - var default_locale: String = DialogueSettings.get_setting("default_csv_locale", "en") + var default_locale: String = DMSettings.get_setting(DMSettings.DEFAULT_CSV_LOCALE, "en") var file: FileAccess @@ -622,32 +605,31 @@ func export_translations_to_csv(path: String) -> void: existing_csv[line[0]] = line # The character column wasn't found in the existing file but the setting is turned on - if character_column == -1 and DialogueSettings.get_setting("include_character_in_translation_exports", false): + if character_column == -1 and DMSettings.get_setting(DMSettings.INCLUDE_CHARACTER_IN_TRANSLATION_EXPORTS, false): character_column = column_count column_count += 1 existing_csv["keys"].append("_character") # The notes column wasn't found in the existing file but the setting is turned on - if notes_column == -1 and DialogueSettings.get_setting("include_notes_in_translation_exports", false): + if notes_column == -1 and DMSettings.get_setting(DMSettings.INCLUDE_NOTES_IN_TRANSLATION_EXPORTS, false): notes_column = column_count column_count += 1 existing_csv["keys"].append("_notes") - - # include tags - if tags_coloumn == -1: - tags_coloumn = column_count - column_count += 1 - existing_csv["keys"].append("_tags") + # include tags + if tags_coloumn == -1: + tags_coloumn = column_count + column_count += 1 + existing_csv["keys"].append("_tags") # Start a new file file = FileAccess.open(path, FileAccess.WRITE) if not FileAccess.file_exists(path): - var headings: PackedStringArray = ["keys", default_locale] - if DialogueSettings.get_setting("include_character_in_translation_exports", false): + var headings: PackedStringArray = ["keys", default_locale] + DMSettings.get_setting(DMSettings.EXTRA_CSV_LOCALES, []) + if DMSettings.get_setting(DMSettings.INCLUDE_CHARACTER_IN_TRANSLATION_EXPORTS, false): character_column = headings.size() headings.append("_character") - if DialogueSettings.get_setting("include_notes_in_translation_exports", false): + if DMSettings.get_setting(DMSettings.INCLUDE_NOTES_IN_TRANSLATION_EXPORTS, false): notes_column = headings.size() headings.append("_notes") tags_coloumn = headings.size() @@ -658,30 +640,33 @@ func export_translations_to_csv(path: String) -> void: # Write our translations to file var known_keys: PackedStringArray = [] - var dialogue: Dictionary = DialogueManagerParser.parse_string(code_edit.text, current_file_path).lines + var dialogue = DMCompiler.compile_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 + if not line.type in [DMConstants.TYPE_DIALOGUE, DMConstants.TYPE_RESPONSE]: continue - known_keys.append(line.translation_key) + var translation_key: String = line.get(&"translation_key", line.text) + + if translation_key in known_keys: continue + + known_keys.append(translation_key) var line_to_save: PackedStringArray = [] - if existing_csv.has(line.translation_key): - line_to_save = existing_csv.get(line.translation_key) + if existing_csv.has(translation_key): + line_to_save = existing_csv.get(translation_key) line_to_save.resize(column_count) - existing_csv.erase(line.translation_key) + existing_csv.erase(translation_key) else: line_to_save.resize(column_count) - line_to_save[0] = line.translation_key + line_to_save[0] = translation_key line_to_save[default_locale_column] = line.text if character_column > -1: - line_to_save[character_column] = "(response)" if line.type == DialogueConstants.TYPE_RESPONSE else line.character + line_to_save[character_column] = "(response)" if line.type == DMConstants.TYPE_RESPONSE else line.character if notes_column > -1: line_to_save[notes_column] = line.notes if tags_coloumn > -1 and line.tags.size() > 0: @@ -697,13 +682,13 @@ func export_translations_to_csv(path: String) -> void: file.close() - plugin.get_editor_interface().get_resource_filesystem().scan() - plugin.get_editor_interface().get_file_system_dock().call_deferred("navigate_to_path", path) + EditorInterface.get_resource_filesystem().scan() + EditorInterface.get_file_system_dock().call_deferred("navigate_to_path", path) # Add it to the project l10n settings if it's not already there - # var language_code: RegExMatch = RegEx.create_from_string("^[a-z]{2,3}").search(default_locale) - # var translation_path: String = path.replace(".csv", ".%s.translation" % language_code.get_string()) - # call_deferred("add_path_to_project_translations", translation_path) + var language_code: RegExMatch = RegEx.create_from_string("^[a-z]{2,3}").search(default_locale) + var translation_path: String = path.replace(".csv", ".%s.translation" % language_code.get_string()) + call_deferred("add_path_to_project_translations", translation_path) func export_character_names_to_csv(path: String) -> void: @@ -730,12 +715,12 @@ func export_character_names_to_csv(path: String) -> void: file = FileAccess.open(path, FileAccess.WRITE) if not file.file_exists(path): - file.store_csv_line(["keys", DialogueSettings.get_setting("default_csv_locale", "en")]) + file.store_csv_line(["keys", DMSettings.get_setting(DMSettings.DEFAULT_CSV_LOCALE, "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 + var character_names: PackedStringArray = DMCompiler.compile_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 = [] @@ -760,12 +745,12 @@ func export_character_names_to_csv(path: String) -> void: file.close() - plugin.get_editor_interface().get_resource_filesystem().scan() - plugin.get_editor_interface().get_file_system_dock().call_deferred("navigate_to_path", path) + EditorInterface.get_resource_filesystem().scan() + EditorInterface.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) + # # 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 @@ -777,7 +762,6 @@ func import_translations_from_csv(path: String) -> void: # Open the CSV file and build a dictionary of the known keys var keys: Dictionary = {} var file: FileAccess = FileAccess.open(path, FileAccess.READ) - var tag_col_index := -1 var tags: Dictionary = {} # get tags index from csv's first line @@ -787,7 +771,6 @@ func import_translations_from_csv(path: String) -> void: if first_line[i] == "_tags": tag_col_index = i break - var csv_line: Array while !file.eof_reached(): csv_line = file.get_csv_line() @@ -796,17 +779,15 @@ func import_translations_from_csv(path: String) -> void: if tag_col_index >= 0 and csv_line.size() > tag_col_index: tags[csv_line[0]] = csv_line[tag_col_index] - 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) + var translation_key: String = DMCompiler.get_static_line_id(line) if keys.has(translation_key): - if parser.is_dialogue_line(line): + if DMCompiler.get_line_type(line) == DMConstants.TYPE_DIALOGUE: start_index = 0 # See if we need to skip over a character name line = line.replace("\\:", "!ESCAPED_COLON!") @@ -820,7 +801,7 @@ func import_translations_from_csv(path: String) -> void: line_text += " " + tag_str + " " lines[i] = (line_text + " [ID:" + translation_key + "]").replace("!ESCAPED_COLON!", ":") - elif parser.is_response_line(line): + elif DMCompiler.get_line_type(line) == DMConstants.TYPE_RESPONSE: start_index = line.find("- ") + 2 # See if we need to skip over a character name line = line.replace("\\:", "!ESCAPED_COLON!") @@ -835,11 +816,7 @@ func import_translations_from_csv(path: String) -> void: code_edit.text = "\n".join(lines) code_edit.set_cursor(cursor) - - parser.free() - # Re-parse the dialogue to update the titles _on_code_edit_text_changed() - func show_search_form(is_enabled: bool) -> void: @@ -864,15 +841,15 @@ func _on_files_moved(old_file: String, new_file: String) -> void: func _on_cache_file_content_changed(path: String, new_content: String) -> void: if open_buffers.has(path): var buffer = open_buffers[path] - if buffer.text != new_content: + if buffer.text == buffer.pristine_text and buffer.text != new_content: buffer.text = new_content - buffer.pristine_text = new_content code_edit.text = new_content title_list.titles = code_edit.get_titles() + buffer.pristine_text = new_content func _on_editor_settings_changed() -> void: - var editor_settings: EditorSettings = plugin.get_editor_interface().get_editor_settings() + var editor_settings: EditorSettings = EditorInterface.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") code_edit.scroll_smooth = editor_settings.get_setting("text_editor/behavior/navigation/smooth_scrolling") @@ -883,11 +860,11 @@ func _on_open_menu_id_pressed(id: int) -> void: OPEN_OPEN: open_dialog.popup_centered() OPEN_QUICK: - quick_open_files_list.files = Engine.get_meta("DialogueCache").get_files() + quick_open_files_list.files = Engine.get_meta("DMCache").get_files() quick_open_dialog.popup_centered() quick_open_files_list.focus_filter() OPEN_CLEAR: - DialogueSettings.clear_recent_files() + DMSettings.clear_recent_files() build_open_menu() _: var menu = open_button.get_popup() @@ -950,7 +927,7 @@ func _on_translations_button_menu_id_pressed(id: int) -> void: func _on_export_dialog_file_selected(path: String) -> void: - DialogueSettings.set_user_value("last_export_path", path.get_base_dir()) + DMSettings.set_user_value("last_export_path", path.get_base_dir()) match path.get_extension(): "csv": match translation_source: @@ -961,7 +938,7 @@ func _on_export_dialog_file_selected(path: String) -> void: func _on_import_dialog_file_selected(path: String) -> void: - DialogueSettings.set_user_value("last_export_path", path.get_base_dir()) + DMSettings.set_user_value("last_export_path", path.get_base_dir()) import_translations_from_csv(path) @@ -1037,11 +1014,10 @@ func _on_code_edit_text_changed() -> void: 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()) + DMSettings.set_caret(current_file_path, code_edit.get_cursor()) func _on_code_edit_error_clicked(line_number: int) -> void: @@ -1055,11 +1031,11 @@ func _on_title_list_title_selected(title: String) -> void: func _on_parse_timer_timeout() -> void: parse_timer.stop() - parse() + compile() func _on_errors_panel_error_pressed(line_number: int, column_number: int) -> void: - code_edit.set_caret_line(line_number) + code_edit.set_caret_line(line_number - 1) code_edit.set_caret_column(column_number) code_edit.grab_focus() @@ -1078,35 +1054,39 @@ func _on_search_and_replace_close_requested() -> void: code_edit.grab_focus() -func _on_settings_button_pressed() -> void: - settings_view.prepare() - settings_dialog.popup_centered() - - -func _on_settings_view_script_button_pressed(path: String) -> void: - settings_dialog.hide() - plugin.get_editor_interface().edit_resource(load(path)) - - func _on_test_button_pressed() -> void: save_file(current_file_path, false) - Engine.get_meta("DialogueCache").reimport_files([current_file_path]) + Engine.get_meta("DMCache").reimport_files([current_file_path]) 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") - plugin.get_editor_interface().play_custom_scene(test_scene_path) + DMSettings.set_user_value("run_title", "") + DMSettings.set_user_value("is_running_test_scene", true) + DMSettings.set_user_value("run_resource_path", current_file_path) + var test_scene_path: String = DMSettings.get_setting(DMSettings.CUSTOM_TEST_SCENE_PATH, "res://addons/dialogue_manager/test_scene.tscn") + EditorInterface.play_custom_scene(test_scene_path) -func _on_settings_dialog_confirmed() -> void: - settings_view.apply_settings_changes() - 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_test_line_button_pressed() -> void: + save_file(current_file_path) + + if errors_panel.errors.size() > 0: + errors_dialog.popup_centered() + return + + # Find next non-empty line + var line_to_run: int = 0 + for i in range(code_edit.get_cursor().y, code_edit.get_line_count()): + if not code_edit.get_line(i).is_empty(): + line_to_run = i + break; + DMSettings.set_user_value("run_title", str(line_to_run)) + DMSettings.set_user_value("is_running_test_scene", true) + DMSettings.set_user_value("run_resource_path", current_file_path) + var test_scene_path: String = DMSettings.get_setting(DMSettings.CUSTOM_TEST_SCENE_PATH, "res://addons/dialogue_manager/test_scene.tscn") + EditorInterface.play_custom_scene(test_scene_path) func _on_support_button_pressed() -> void: @@ -1131,14 +1111,14 @@ func _on_files_popup_menu_about_to_popup() -> void: var shortcuts: Dictionary = plugin.get_editor_shortcuts() - files_popup_menu.add_item(DialogueConstants.translate(&"buffer.save"), ITEM_SAVE, OS.find_keycode_from_string(shortcuts.get("save")[0].as_text_keycode())) - files_popup_menu.add_item(DialogueConstants.translate(&"buffer.save_as"), ITEM_SAVE_AS) - files_popup_menu.add_item(DialogueConstants.translate(&"buffer.close"), ITEM_CLOSE, OS.find_keycode_from_string(shortcuts.get("close_file")[0].as_text_keycode())) - 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_item(DMConstants.translate(&"buffer.save"), ITEM_SAVE, OS.find_keycode_from_string(shortcuts.get("save")[0].as_text_keycode())) + files_popup_menu.add_item(DMConstants.translate(&"buffer.save_as"), ITEM_SAVE_AS) + files_popup_menu.add_item(DMConstants.translate(&"buffer.close"), ITEM_CLOSE, OS.find_keycode_from_string(shortcuts.get("close_file")[0].as_text_keycode())) + files_popup_menu.add_item(DMConstants.translate(&"buffer.close_all"), ITEM_CLOSE_ALL) + files_popup_menu.add_item(DMConstants.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) + files_popup_menu.add_item(DMConstants.translate(&"buffer.copy_file_path"), ITEM_COPY_PATH) + files_popup_menu.add_item(DMConstants.translate(&"buffer.show_in_filesystem"), ITEM_SHOW_IN_FILESYSTEM) func _on_files_popup_menu_id_pressed(id: int) -> void: diff --git a/addons/dialogue_manager/views/main_view.gd.uid b/addons/dialogue_manager/views/main_view.gd.uid new file mode 100644 index 00000000..10e66f49 --- /dev/null +++ b/addons/dialogue_manager/views/main_view.gd.uid @@ -0,0 +1 @@ +uid://cipjcc7bkh1pc diff --git a/addons/dialogue_manager/views/main_view.tscn b/addons/dialogue_manager/views/main_view.tscn index 2ab80044..4e70b263 100644 --- a/addons/dialogue_manager/views/main_view.tscn +++ b/addons/dialogue_manager/views/main_view.tscn @@ -1,17 +1,16 @@ -[gd_scene load_steps=16 format=3 uid="uid://cbuf1q3xsse3q"] +[gd_scene load_steps=15 format=3 uid="uid://cbuf1q3xsse3q"] -[ext_resource type="Script" path="res://addons/dialogue_manager/views/main_view.gd" id="1_h6qfq"] +[ext_resource type="Script" uid="uid://cipjcc7bkh1pc" path="res://addons/dialogue_manager/views/main_view.gd" id="1_h6qfq"] [ext_resource type="PackedScene" uid="uid://civ6shmka5e8u" path="res://addons/dialogue_manager/components/code_edit.tscn" id="2_f73fm"] [ext_resource type="PackedScene" uid="uid://dnufpcdrreva3" path="res://addons/dialogue_manager/components/files_list.tscn" id="2_npj2k"] [ext_resource type="PackedScene" uid="uid://ctns6ouwwd68i" path="res://addons/dialogue_manager/components/title_list.tscn" id="2_onb4i"] [ext_resource type="PackedScene" uid="uid://co8yl23idiwbi" path="res://addons/dialogue_manager/components/update_button.tscn" id="2_ph3vs"] [ext_resource type="PackedScene" uid="uid://gr8nakpbrhby" path="res://addons/dialogue_manager/components/search_and_replace.tscn" id="6_ylh0t"] [ext_resource type="PackedScene" uid="uid://cs8pwrxr5vxix" path="res://addons/dialogue_manager/components/errors_panel.tscn" id="7_5cvl4"] -[ext_resource type="Script" path="res://addons/dialogue_manager/components/code_edit_syntax_highlighter.gd" id="7_necsa"] -[ext_resource type="PackedScene" uid="uid://cpg4lg1r3ff6m" path="res://addons/dialogue_manager/views/settings_view.tscn" id="9_8bf36"] +[ext_resource type="Script" uid="uid://klpiq4tk3t7a" path="res://addons/dialogue_manager/components/code_edit_syntax_highlighter.gd" id="7_necsa"] [ext_resource type="PackedScene" uid="uid://0n7hwviyyly4" path="res://addons/dialogue_manager/components/find_in_files.tscn" id="10_yold3"] -[sub_resource type="Image" id="Image_68x0r"] +[sub_resource type="Image" id="Image_faxki"] data = { "data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0), "format": "RGBA8", @@ -21,9 +20,9 @@ data = { } [sub_resource type="ImageTexture" id="ImageTexture_ka3gk"] -image = SubResource("Image_68x0r") +image = SubResource("Image_faxki") -[sub_resource type="Image" id="Image_j5bhl"] +[sub_resource type="Image" id="Image_y6rqu"] data = { "data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0), "format": "RGBA8", @@ -33,9 +32,9 @@ data = { } [sub_resource type="ImageTexture" id="ImageTexture_57eek"] -image = SubResource("Image_j5bhl") +image = SubResource("Image_y6rqu") -[sub_resource type="SyntaxHighlighter" id="SyntaxHighlighter_bxc68"] +[sub_resource type="SyntaxHighlighter" id="SyntaxHighlighter_kb7f8"] script = ExtResource("7_necsa") [node name="MainView" type="Control"] @@ -233,6 +232,9 @@ disabled = true toggle_mode = true flat = true +[node name="Separator2" type="VSeparator" parent="Margin/Content/CodePanel/Toolbar"] +layout_mode = 2 + [node name="TestButton" type="Button" parent="Margin/Content/CodePanel/Toolbar"] unique_name_in_owner = true layout_mode = 2 @@ -240,13 +242,11 @@ tooltip_text = "Test dialogue" disabled = true flat = true -[node name="Separator3" type="VSeparator" parent="Margin/Content/CodePanel/Toolbar"] -layout_mode = 2 - -[node name="SettingsButton" type="Button" parent="Margin/Content/CodePanel/Toolbar"] +[node name="TestLineButton" type="Button" parent="Margin/Content/CodePanel/Toolbar"] unique_name_in_owner = true layout_mode = 2 -tooltip_text = "Settings" +tooltip_text = "Test dialogue" +disabled = true flat = true [node name="Spacer2" type="Control" parent="Margin/Content/CodePanel/Toolbar"] @@ -279,6 +279,7 @@ vertical_alignment = 1 [node name="UpdateButton" parent="Margin/Content/CodePanel/Toolbar" instance=ExtResource("2_ph3vs")] unique_name_in_owner = true layout_mode = 2 +text = "v2.44.1 available" [node name="SearchAndReplace" parent="Margin/Content/CodePanel" instance=ExtResource("6_ylh0t")] unique_name_in_owner = true @@ -311,7 +312,7 @@ Coco: Meow. => END" scroll_smooth = true -syntax_highlighter = SubResource("SyntaxHighlighter_bxc68") +syntax_highlighter = SubResource("SyntaxHighlighter_kb7f8") [node name="ErrorsPanel" parent="Margin/Content/CodePanel" instance=ExtResource("7_5cvl4")] unique_name_in_owner = true @@ -362,20 +363,6 @@ filters = PackedStringArray("*.csv ; Translation CSV") 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(1000, 600) -unresizable = true -min_size = Vector2i(1000, 600) -max_size = Vector2i(1000, 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." @@ -419,7 +406,7 @@ code_edit = NodePath("../../Margin/Content/CodePanel/CodeEdit") [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/TestLineButton" to="." method="_on_test_line_button_pressed"] [connection signal="pressed" from="Margin/Content/CodePanel/Toolbar/SupportButton" to="." method="_on_support_button_pressed"] [connection signal="pressed" from="Margin/Content/CodePanel/Toolbar/DocsButton" to="." method="_on_docs_button_pressed"] [connection signal="close_requested" from="Margin/Content/CodePanel/SearchAndReplace" to="." method="_on_search_and_replace_close_requested"] @@ -438,8 +425,6 @@ code_edit = NodePath("../../Margin/Content/CodePanel/CodeEdit") [connection signal="file_double_clicked" from="QuickOpenDialog/QuickOpenFilesList" to="." method="_on_quick_open_files_list_file_double_clicked"] [connection signal="file_selected" from="ExportDialog" to="." method="_on_export_dialog_file_selected"] [connection signal="file_selected" from="ImportDialog" to="." method="_on_import_dialog_file_selected"] -[connection signal="confirmed" from="SettingsDialog" to="." method="_on_settings_dialog_confirmed"] -[connection signal="script_button_pressed" from="SettingsDialog/SettingsView" to="." method="_on_settings_view_script_button_pressed"] [connection signal="confirmed" from="CloseConfirmationDialog" to="." method="_on_close_confirmation_dialog_confirmed"] [connection signal="custom_action" from="CloseConfirmationDialog" to="." method="_on_close_confirmation_dialog_custom_action"] [connection signal="result_selected" from="FindInFilesDialog/FindInFiles" to="." method="_on_find_in_files_result_selected"] diff --git a/addons/dialogue_manager/views/settings_view.gd b/addons/dialogue_manager/views/settings_view.gd deleted file mode 100644 index 800464dd..00000000 --- a/addons/dialogue_manager/views/settings_view.gd +++ /dev/null @@ -1,288 +0,0 @@ -@tool -extends TabContainer - - -signal script_button_pressed(path: String) - - -const DialogueConstants = preload("../constants.gd") -const DialogueSettings = preload("../settings.gd") -const BaseDialogueTestScene = preload("../test_scene.gd") - - -enum PathTarget { - CustomTestScene, - Balloon -} - - -# Editor -@onready var new_template_button: CheckBox = $Editor/NewTemplateButton -@onready var new_template: CodeEdit = $Editor/NewTemplate -@onready var characters_translations_button: CheckBox = $Editor/CharactersTranslationsButton -@onready var wrap_lines_button: Button = $Editor/WrapLinesButton -@onready var default_csv_locale: LineEdit = $Editor/DefaultCSVLocale - -# Runtime -@onready var include_all_responses_button: CheckBox = $Runtime/IncludeAllResponsesButton -@onready var ignore_missing_state_values: CheckBox = $Runtime/IgnoreMissingStateValues -@onready var balloon_path_input: LineEdit = $Runtime/CustomBalloon/BalloonPath -@onready var revert_balloon_button: Button = $Runtime/CustomBalloon/RevertBalloonPath -@onready var load_balloon_button: Button = $Runtime/CustomBalloon/LoadBalloonPath -@onready var states_title: Label = $Runtime/StatesTitle -@onready var globals_list: Tree = $Runtime/GlobalsList - -# Advanced -@onready var check_for_updates: CheckBox = $Advanced/CheckForUpdates -@onready var include_characters_in_translations: CheckBox = $Advanced/IncludeCharactersInTranslations -@onready var include_notes_in_translations: CheckBox = $Advanced/IncludeNotesInTranslations -@onready var open_in_external_editor_button: CheckBox = $Advanced/OpenInExternalEditorButton -@onready var test_scene_path_input: LineEdit = $Advanced/CustomTestScene/TestScenePath -@onready var revert_test_scene_button: Button = $Advanced/CustomTestScene/RevertTestScene -@onready var load_test_scene_button: Button = $Advanced/CustomTestScene/LoadTestScene -@onready var custom_test_scene_file_dialog: FileDialog = $CustomTestSceneFileDialog -@onready var create_lines_for_response_characters: CheckBox = $Advanced/CreateLinesForResponseCharacters -@onready var missing_translations_button: CheckBox = $Advanced/MissingTranslationsButton - -var all_globals: Dictionary = {} -var enabled_globals: Array = [] -var path_target: PathTarget = PathTarget.CustomTestScene - -var _default_test_scene_path: String = preload("../test_scene.tscn").resource_path - -var _recompile_if_changed_settings: Dictionary - - -func _ready() -> void: - new_template_button.text = DialogueConstants.translate(&"settings.new_template") - characters_translations_button.text = DialogueConstants.translate(&"settings.characters_translations") - wrap_lines_button.text = DialogueConstants.translate(&"settings.wrap_long_lines") - $Editor/DefaultCSVLocaleLabel.text = DialogueConstants.translate(&"settings.default_csv_locale") - - include_all_responses_button.text = DialogueConstants.translate(&"settings.include_failed_responses") - ignore_missing_state_values.text = DialogueConstants.translate(&"settings.ignore_missing_state_values") - $Runtime/CustomBalloonLabel.text = DialogueConstants.translate(&"settings.default_balloon_hint") - states_title.text = DialogueConstants.translate(&"settings.states_shortcuts") - $Runtime/StatesMessage.text = DialogueConstants.translate(&"settings.states_message") - $Runtime/StatesHint.text = DialogueConstants.translate(&"settings.states_hint") - - check_for_updates.text = DialogueConstants.translate(&"settings.check_for_updates") - include_characters_in_translations.text = DialogueConstants.translate(&"settings.include_characters_in_translations") - include_notes_in_translations.text = DialogueConstants.translate(&"settings.include_notes_in_translations") - open_in_external_editor_button.text = DialogueConstants.translate(&"settings.open_in_external_editor") - $Advanced/ExternalWarning.text = DialogueConstants.translate(&"settings.external_editor_warning") - $Advanced/CustomTestSceneLabel.text = DialogueConstants.translate(&"settings.custom_test_scene") - $Advanced/RecompileWarning.text = DialogueConstants.translate(&"settings.recompile_warning") - missing_translations_button.text = DialogueConstants.translate(&"settings.missing_keys") - $Advanced/MissingTranslationsHint.text = DialogueConstants.translate(&"settings.missing_keys_hint") - create_lines_for_response_characters.text = DialogueConstants.translate(&"settings.create_lines_for_responses_with_characters") - - current_tab = 0 - - -func prepare() -> void: - _recompile_if_changed_settings = _get_settings_that_require_recompilation() - - test_scene_path_input.placeholder_text = DialogueSettings.get_setting("custom_test_scene_path", _default_test_scene_path) - revert_test_scene_button.visible = test_scene_path_input.placeholder_text != _default_test_scene_path - revert_test_scene_button.icon = get_theme_icon("RotateLeft", "EditorIcons") - revert_test_scene_button.tooltip_text = DialogueConstants.translate(&"settings.revert_to_default_test_scene") - load_test_scene_button.icon = get_theme_icon("Load", "EditorIcons") - - var balloon_path: String = DialogueSettings.get_setting("balloon_path", "") - if not FileAccess.file_exists(balloon_path): - DialogueSettings.set_setting("balloon_path", "") - balloon_path = "" - balloon_path_input.placeholder_text = balloon_path if balloon_path != "" else DialogueConstants.translate(&"settings.default_balloon_path") - revert_balloon_button.visible = balloon_path != "" - revert_balloon_button.icon = get_theme_icon("RotateLeft", "EditorIcons") - revert_balloon_button.tooltip_text = DialogueConstants.translate(&"settings.revert_to_default_balloon") - load_balloon_button.icon = get_theme_icon("Load", "EditorIcons") - - var scale: float = Engine.get_meta("DialogueManagerPlugin").get_editor_interface().get_editor_scale() - custom_test_scene_file_dialog.min_size = Vector2(600, 500) * scale - - states_title.add_theme_font_override("font", get_theme_font("bold", "EditorFonts")) - - check_for_updates.set_pressed_no_signal(DialogueSettings.get_user_value("check_for_updates", true)) - characters_translations_button.set_pressed_no_signal(DialogueSettings.get_setting("export_characters_in_translation", true)) - wrap_lines_button.set_pressed_no_signal(DialogueSettings.get_setting("wrap_lines", false)) - include_all_responses_button.set_pressed_no_signal(DialogueSettings.get_setting("include_all_responses", false)) - ignore_missing_state_values.set_pressed_no_signal(DialogueSettings.get_setting("ignore_missing_state_values", false)) - new_template_button.set_pressed_no_signal(DialogueSettings.get_setting("new_with_template", true)) - new_template.text = DialogueSettings.get_setting("new_template", "") - new_template.visible = DialogueSettings.get_setting("new_with_template", true) - default_csv_locale.text = DialogueSettings.get_setting("default_csv_locale", "en") - - missing_translations_button.set_pressed_no_signal(DialogueSettings.get_setting("missing_translations_are_errors", false)) - create_lines_for_response_characters.set_pressed_no_signal(DialogueSettings.get_setting("create_lines_for_responses_with_characters", true)) - - include_characters_in_translations.set_pressed_no_signal(DialogueSettings.get_setting("include_character_in_translation_exports", false)) - include_notes_in_translations.set_pressed_no_signal(DialogueSettings.get_setting("include_notes_in_translation_exports", false)) - open_in_external_editor_button.set_pressed_no_signal(DialogueSettings.get_user_value("open_in_external_editor", false)) - - var editor_settings: EditorSettings = Engine.get_meta("DialogueManagerPlugin").get_editor_interface().get_editor_settings() - var external_editor: String = editor_settings.get_setting("text_editor/external/exec_path") - var use_external_editor: bool = editor_settings.get_setting("text_editor/external/use_external_editor") and external_editor != "" - if not use_external_editor: - open_in_external_editor_button.hide() - $Advanced/ExternalWarning.hide() - $Advanced/ExternalSeparator.hide() - - var project = ConfigFile.new() - var err = project.load("res://project.godot") - assert(err == OK, "Could not find the project file") - - all_globals.clear() - if project.has_section("autoload"): - for key in project.get_section_keys("autoload"): - if key != "DialogueManager": - all_globals[key] = project.get_value("autoload", key) - - enabled_globals = DialogueSettings.get_setting("states", []).duplicate() - globals_list.clear() - var root = globals_list.create_item() - for name in all_globals.keys(): - var item: TreeItem = globals_list.create_item(root) - item.set_cell_mode(0, TreeItem.CELL_MODE_CHECK) - item.set_checked(0, name in enabled_globals) - item.set_text(0, name) - item.add_button(1, get_theme_icon("Edit", "EditorIcons")) - item.set_text(2, all_globals.get(name, "").replace("*res://", "res://")) - - globals_list.set_column_expand(0, false) - globals_list.set_column_custom_minimum_width(0, 250) - globals_list.set_column_expand(1, false) - globals_list.set_column_custom_minimum_width(1, 40) - globals_list.set_column_titles_visible(true) - globals_list.set_column_title(0, DialogueConstants.translate(&"settings.autoload")) - globals_list.set_column_title(1, "") - globals_list.set_column_title(2, DialogueConstants.translate(&"settings.path")) - - -func apply_settings_changes() -> void: - if _recompile_if_changed_settings != _get_settings_that_require_recompilation(): - Engine.get_meta("DialogueCache").reimport_files() - - -func _get_settings_that_require_recompilation() -> Dictionary: - return DialogueSettings.get_settings([ - "missing_translations_are_errors", - "create_lines_for_responses_with_characters" - ]) - - -### Signals - - -func _on_missing_translations_button_toggled(toggled_on: bool) -> void: - DialogueSettings.set_setting("missing_translations_are_errors", toggled_on) - - -func _on_characters_translations_button_toggled(toggled_on: bool) -> void: - DialogueSettings.set_setting("export_characters_in_translation", toggled_on) - - -func _on_wrap_lines_button_toggled(toggled_on: bool) -> void: - DialogueSettings.set_setting("wrap_lines", toggled_on) - - -func _on_include_all_responses_button_toggled(toggled_on: bool) -> void: - DialogueSettings.set_setting("include_all_responses", toggled_on) - - -func _on_globals_list_item_selected() -> void: - var item = globals_list.get_selected() - var is_checked = not item.is_checked(0) - item.set_checked(0, is_checked) - - if is_checked: - enabled_globals.append(item.get_text(0)) - else: - enabled_globals.erase(item.get_text(0)) - - DialogueSettings.set_setting("states", enabled_globals) - - -func _on_globals_list_button_clicked(item: TreeItem, column: int, id: int, mouse_button_index: int) -> void: - emit_signal("script_button_pressed", item.get_text(2)) - - -func _on_sample_template_toggled(toggled_on): - DialogueSettings.set_setting("new_with_template", toggled_on) - new_template.visible = toggled_on - - -func _on_revert_test_scene_pressed() -> void: - DialogueSettings.set_setting("custom_test_scene_path", _default_test_scene_path) - test_scene_path_input.placeholder_text = _default_test_scene_path - revert_test_scene_button.visible = test_scene_path_input.placeholder_text != _default_test_scene_path - - -func _on_load_test_scene_pressed() -> void: - path_target = PathTarget.CustomTestScene - custom_test_scene_file_dialog.popup_centered() - - -func _on_custom_test_scene_file_dialog_file_selected(path: String) -> void: - match path_target: - PathTarget.CustomTestScene: - # Check that the test scene is a subclass of BaseDialogueTestScene - var test_scene: PackedScene = load(path) - if test_scene and test_scene.instantiate() is BaseDialogueTestScene: - DialogueSettings.set_setting("custom_test_scene_path", path) - test_scene_path_input.placeholder_text = path - revert_test_scene_button.visible = test_scene_path_input.placeholder_text != _default_test_scene_path - else: - var accept: AcceptDialog = AcceptDialog.new() - accept.dialog_text = DialogueConstants.translate(&"settings.invalid_test_scene").format({ path = path }) - add_child(accept) - accept.popup_centered.call_deferred() - - PathTarget.Balloon: - DialogueSettings.set_setting("balloon_path", path) - balloon_path_input.placeholder_text = path - revert_balloon_button.visible = balloon_path_input.placeholder_text != "" - - -func _on_ignore_missing_state_values_toggled(toggled_on: bool) -> void: - DialogueSettings.set_setting("ignore_missing_state_values", toggled_on) - - -func _on_default_csv_locale_text_changed(new_text: String) -> void: - DialogueSettings.set_setting("default_csv_locale", new_text) - - -func _on_revert_balloon_path_pressed() -> void: - DialogueSettings.set_setting("balloon_path", "") - balloon_path_input.placeholder_text = DialogueConstants.translate(&"settings.default_balloon_path") - revert_balloon_button.visible = DialogueSettings.get_setting("balloon_path", "") != "" - - -func _on_load_balloon_path_pressed() -> void: - path_target = PathTarget.Balloon - custom_test_scene_file_dialog.popup_centered() - - -func _on_create_lines_for_response_characters_toggled(toggled_on: bool) -> void: - DialogueSettings.set_setting("create_lines_for_responses_with_characters", toggled_on) - - -func _on_open_in_external_editor_button_toggled(toggled_on: bool) -> void: - DialogueSettings.set_user_value("open_in_external_editor", toggled_on) - - -func _on_include_characters_in_translations_toggled(toggled_on: bool) -> void: - DialogueSettings.set_setting("include_character_in_translation_exports", toggled_on) - - -func _on_include_notes_in_translations_toggled(toggled_on: bool) -> void: - DialogueSettings.set_setting("include_notes_in_translation_exports", toggled_on) - - -func _on_keep_up_to_date_toggled(toggled_on: bool) -> void: - DialogueSettings.set_user_value("check_for_updates", toggled_on) - - -func _on_new_template_focus_exited() -> void: - DialogueSettings.set_setting("new_template", new_template.text) diff --git a/addons/dialogue_manager/views/settings_view.tscn b/addons/dialogue_manager/views/settings_view.tscn deleted file mode 100644 index 626010d6..00000000 --- a/addons/dialogue_manager/views/settings_view.tscn +++ /dev/null @@ -1,233 +0,0 @@ -[gd_scene load_steps=3 format=3 uid="uid://cpg4lg1r3ff6m"] - -[ext_resource type="Script" path="res://addons/dialogue_manager/views/settings_view.gd" id="1_06uxa"] - -[sub_resource type="Theme" id="Theme_3a8rc"] -HSeparator/constants/separation = 20 - -[node name="SettingsView" type="TabContainer"] -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_right = -206.0 -offset_bottom = -345.0 -grow_horizontal = 2 -grow_vertical = 2 -size_flags_horizontal = 3 -size_flags_vertical = 3 -theme = SubResource("Theme_3a8rc") -current_tab = 0 -script = ExtResource("1_06uxa") - -[node name="Editor" type="VBoxContainer" parent="."] -layout_mode = 2 -metadata/_tab_index = 0 - -[node name="NewTemplateButton" type="CheckBox" parent="Editor"] -layout_mode = 2 -button_pressed = true -text = "New dialogue files will start with template text" - -[node name="NewTemplate" type="CodeEdit" parent="Editor"] -custom_minimum_size = Vector2(0, 200) -layout_mode = 2 -size_flags_vertical = 3 - -[node name="HSeparator" type="HSeparator" parent="Editor"] -layout_mode = 2 - -[node name="CharactersTranslationsButton" type="CheckBox" parent="Editor"] -layout_mode = 2 -button_pressed = true -text = "Export character names in translation files" - -[node name="WrapLinesButton" type="CheckBox" parent="Editor"] -layout_mode = 2 -button_pressed = true -text = "Wrap long lines" - -[node name="HSeparator2" type="HSeparator" parent="Editor"] -layout_mode = 2 - -[node name="DefaultCSVLocaleLabel" type="Label" parent="Editor"] -layout_mode = 2 -text = "Default CSV Locale" - -[node name="DefaultCSVLocale" type="LineEdit" parent="Editor"] -layout_mode = 2 - -[node name="Runtime" type="VBoxContainer" parent="."] -visible = false -layout_mode = 2 -metadata/_tab_index = 1 - -[node name="IncludeAllResponsesButton" type="CheckBox" parent="Runtime"] -layout_mode = 2 -text = "Include responses with failed conditions" - -[node name="IgnoreMissingStateValues" type="CheckBox" parent="Runtime"] -layout_mode = 2 -text = "Skip over missing state value errors (not recommended)" - -[node name="HSeparator" type="HSeparator" parent="Runtime"] -layout_mode = 2 - -[node name="CustomBalloonLabel" type="Label" parent="Runtime"] -layout_mode = 2 -text = "Custom balloon to use when calling \"DialogueManager.show_balloon()\"" - -[node name="CustomBalloon" type="HBoxContainer" parent="Runtime"] -layout_mode = 2 - -[node name="BalloonPath" type="LineEdit" parent="Runtime/CustomBalloon"] -layout_mode = 2 -size_flags_horizontal = 3 -focus_mode = 0 -editable = false -shortcut_keys_enabled = false -middle_mouse_paste_enabled = false - -[node name="RevertBalloonPath" type="Button" parent="Runtime/CustomBalloon"] -visible = false -layout_mode = 2 -tooltip_text = "Revert to default test scene" -flat = true - -[node name="LoadBalloonPath" type="Button" parent="Runtime/CustomBalloon"] -layout_mode = 2 - -[node name="HSeparator2" type="HSeparator" parent="Runtime"] -layout_mode = 2 - -[node name="StatesTitle" type="Label" parent="Runtime"] -layout_mode = 2 -text = "State Shortcuts" - -[node name="StatesMessage" type="Label" parent="Runtime"] -layout_mode = 2 -text = "If an autoload is enabled here you can refer to its properties, methods, and signals without having to use its name." - -[node name="StatesHint" type="Label" parent="Runtime"] -modulate = Color(1, 1, 1, 0.501961) -custom_minimum_size = Vector2(10, 0) -layout_mode = 2 -text = "ie. Instead of \"SomeState.some_property\" you could just use \"some_property\"" -autowrap_mode = 3 - -[node name="GlobalsList" type="Tree" parent="Runtime"] -layout_mode = 2 -size_flags_vertical = 3 -columns = 3 -column_titles_visible = true -allow_reselect = true -hide_folding = true -hide_root = true -select_mode = 1 - -[node name="Advanced" type="VBoxContainer" parent="."] -visible = false -layout_mode = 2 -metadata/_tab_index = 2 - -[node name="CheckForUpdates" type="CheckBox" parent="Advanced"] -layout_mode = 2 -text = "Check for updates" - -[node name="HSeparator" type="HSeparator" parent="Advanced"] -layout_mode = 2 - -[node name="IncludeCharactersInTranslations" type="CheckBox" parent="Advanced"] -layout_mode = 2 -text = "Include character names in translation exports" - -[node name="IncludeNotesInTranslations" type="CheckBox" parent="Advanced"] -layout_mode = 2 -text = "Include notes (## comments) in translation exports" - -[node name="ExternalSeparator" type="HSeparator" parent="Advanced"] -layout_mode = 2 - -[node name="OpenInExternalEditorButton" type="CheckBox" parent="Advanced"] -layout_mode = 2 -text = "Open dialogue files in external editor" - -[node name="ExternalWarning" type="Label" parent="Advanced"] -layout_mode = 2 -text = "Note: Syntax highlighting and detailed error checking are not supported in external editors." - -[node name="HSeparator3" type="HSeparator" parent="Advanced"] -layout_mode = 2 - -[node name="CustomTestSceneLabel" type="Label" parent="Advanced"] -layout_mode = 2 -text = "Custom test scene (must extend BaseDialogueTestScene)" - -[node name="CustomTestScene" type="HBoxContainer" parent="Advanced"] -layout_mode = 2 - -[node name="TestScenePath" type="LineEdit" parent="Advanced/CustomTestScene"] -layout_mode = 2 -size_flags_horizontal = 3 -focus_mode = 0 -placeholder_text = "res://addons/dialogue_manager/test_scene.tscn" -editable = false -shortcut_keys_enabled = false -middle_mouse_paste_enabled = false - -[node name="RevertTestScene" type="Button" parent="Advanced/CustomTestScene"] -visible = false -layout_mode = 2 -tooltip_text = "Revert to default test scene" -flat = true - -[node name="LoadTestScene" type="Button" parent="Advanced/CustomTestScene"] -layout_mode = 2 - -[node name="HSeparator4" type="HSeparator" parent="Advanced"] -layout_mode = 2 - -[node name="RecompileWarning" type="Label" parent="Advanced"] -layout_mode = 2 -text = "Changing these settings will force a recompile of all dialogue. Only change them if you know what you are doing." - -[node name="MissingTranslationsButton" type="CheckBox" parent="Advanced"] -layout_mode = 2 -text = "Treat missing translation keys as errors" - -[node name="MissingTranslationsHint" type="Label" parent="Advanced"] -modulate = Color(1, 1, 1, 0.501961) -custom_minimum_size = Vector2(10, 0) -layout_mode = 2 -text = "If you are using static translation keys then having this enabled will help you find any lines that you haven't added a key to yet." -autowrap_mode = 3 - -[node name="CreateLinesForResponseCharacters" type="CheckBox" parent="Advanced"] -layout_mode = 2 -text = "Create child dialogue line for responses with character names in them" - -[node name="CustomTestSceneFileDialog" type="FileDialog" parent="."] -title = "Open a File" -ok_button_text = "Open" -file_mode = 0 -filters = PackedStringArray("*.tscn ; Scene") - -[connection signal="toggled" from="Editor/NewTemplateButton" to="." method="_on_sample_template_toggled"] -[connection signal="focus_exited" from="Editor/NewTemplate" to="." method="_on_new_template_focus_exited"] -[connection signal="toggled" from="Editor/CharactersTranslationsButton" to="." method="_on_characters_translations_button_toggled"] -[connection signal="toggled" from="Editor/WrapLinesButton" to="." method="_on_wrap_lines_button_toggled"] -[connection signal="text_changed" from="Editor/DefaultCSVLocale" to="." method="_on_default_csv_locale_text_changed"] -[connection signal="toggled" from="Runtime/IncludeAllResponsesButton" to="." method="_on_include_all_responses_button_toggled"] -[connection signal="toggled" from="Runtime/IgnoreMissingStateValues" to="." method="_on_ignore_missing_state_values_toggled"] -[connection signal="pressed" from="Runtime/CustomBalloon/RevertBalloonPath" to="." method="_on_revert_balloon_path_pressed"] -[connection signal="pressed" from="Runtime/CustomBalloon/LoadBalloonPath" to="." method="_on_load_balloon_path_pressed"] -[connection signal="button_clicked" from="Runtime/GlobalsList" to="." method="_on_globals_list_button_clicked"] -[connection signal="item_selected" from="Runtime/GlobalsList" to="." method="_on_globals_list_item_selected"] -[connection signal="toggled" from="Advanced/CheckForUpdates" to="." method="_on_keep_up_to_date_toggled"] -[connection signal="toggled" from="Advanced/IncludeCharactersInTranslations" to="." method="_on_include_characters_in_translations_toggled"] -[connection signal="toggled" from="Advanced/IncludeNotesInTranslations" to="." method="_on_include_notes_in_translations_toggled"] -[connection signal="toggled" from="Advanced/OpenInExternalEditorButton" to="." method="_on_open_in_external_editor_button_toggled"] -[connection signal="pressed" from="Advanced/CustomTestScene/RevertTestScene" to="." method="_on_revert_test_scene_pressed"] -[connection signal="pressed" from="Advanced/CustomTestScene/LoadTestScene" to="." method="_on_load_test_scene_pressed"] -[connection signal="toggled" from="Advanced/MissingTranslationsButton" to="." method="_on_missing_translations_button_toggled"] -[connection signal="toggled" from="Advanced/CreateLinesForResponseCharacters" to="." method="_on_create_lines_for_response_characters_toggled"] -[connection signal="file_selected" from="CustomTestSceneFileDialog" to="." method="_on_custom_test_scene_file_dialog_file_selected"] diff --git a/addons/gif-importer/importer_plugin.gd.uid b/addons/gif-importer/importer_plugin.gd.uid new file mode 100644 index 00000000..4e0916f0 --- /dev/null +++ b/addons/gif-importer/importer_plugin.gd.uid @@ -0,0 +1 @@ +uid://bmmbknqsu1616 diff --git a/addons/gif-importer/plugin.gd.uid b/addons/gif-importer/plugin.gd.uid new file mode 100644 index 00000000..020dd43b --- /dev/null +++ b/addons/gif-importer/plugin.gd.uid @@ -0,0 +1 @@ +uid://chhfucgvyck0d diff --git a/addons/godotgif/godotgif.gdextension.uid b/addons/godotgif/godotgif.gdextension.uid new file mode 100644 index 00000000..bdcad000 --- /dev/null +++ b/addons/godotgif/godotgif.gdextension.uid @@ -0,0 +1 @@ +uid://bbm88v6od63re diff --git a/addons/project-statistics/loaders/FileStatistics.gd.uid b/addons/project-statistics/loaders/FileStatistics.gd.uid new file mode 100644 index 00000000..ea84fc47 --- /dev/null +++ b/addons/project-statistics/loaders/FileStatistics.gd.uid @@ -0,0 +1 @@ +uid://cuhuljeysmnb6 diff --git a/addons/project-statistics/loaders/ProjectStatistics.gd.uid b/addons/project-statistics/loaders/ProjectStatistics.gd.uid new file mode 100644 index 00000000..22a20575 --- /dev/null +++ b/addons/project-statistics/loaders/ProjectStatistics.gd.uid @@ -0,0 +1 @@ +uid://watilbetk3x4 diff --git a/addons/project-statistics/loaders/extensions/CSharpStatistics.gd.uid b/addons/project-statistics/loaders/extensions/CSharpStatistics.gd.uid new file mode 100644 index 00000000..8bae81a7 --- /dev/null +++ b/addons/project-statistics/loaders/extensions/CSharpStatistics.gd.uid @@ -0,0 +1 @@ +uid://ct4b7lqq2pl1j diff --git a/addons/project-statistics/loaders/extensions/ConfigFileStatistics.gd.uid b/addons/project-statistics/loaders/extensions/ConfigFileStatistics.gd.uid new file mode 100644 index 00000000..a4a4a0f4 --- /dev/null +++ b/addons/project-statistics/loaders/extensions/ConfigFileStatistics.gd.uid @@ -0,0 +1 @@ +uid://bjjydic6vqac2 diff --git a/addons/project-statistics/loaders/extensions/GDScriptStatistics.gd.uid b/addons/project-statistics/loaders/extensions/GDScriptStatistics.gd.uid new file mode 100644 index 00000000..d0daa37e --- /dev/null +++ b/addons/project-statistics/loaders/extensions/GDScriptStatistics.gd.uid @@ -0,0 +1 @@ +uid://c40c1l7c6wtas diff --git a/addons/project-statistics/loaders/extensions/JSONStatistics.gd.uid b/addons/project-statistics/loaders/extensions/JSONStatistics.gd.uid new file mode 100644 index 00000000..b1a95031 --- /dev/null +++ b/addons/project-statistics/loaders/extensions/JSONStatistics.gd.uid @@ -0,0 +1 @@ +uid://cfyyll301ao80 diff --git a/addons/project-statistics/loaders/extensions/MarkdownStatistics.gd.uid b/addons/project-statistics/loaders/extensions/MarkdownStatistics.gd.uid new file mode 100644 index 00000000..f5900e1e --- /dev/null +++ b/addons/project-statistics/loaders/extensions/MarkdownStatistics.gd.uid @@ -0,0 +1 @@ +uid://c385jq6538ns diff --git a/addons/project-statistics/loaders/extensions/ResourceStatistics.gd.uid b/addons/project-statistics/loaders/extensions/ResourceStatistics.gd.uid new file mode 100644 index 00000000..1754a3c5 --- /dev/null +++ b/addons/project-statistics/loaders/extensions/ResourceStatistics.gd.uid @@ -0,0 +1 @@ +uid://bcs8krfetq3qa diff --git a/addons/project-statistics/loaders/extensions/YAMLStatistics.gd.uid b/addons/project-statistics/loaders/extensions/YAMLStatistics.gd.uid new file mode 100644 index 00000000..ab5a0ffe --- /dev/null +++ b/addons/project-statistics/loaders/extensions/YAMLStatistics.gd.uid @@ -0,0 +1 @@ +uid://be5hndydak02 diff --git a/addons/project-statistics/nodes/MiscView.gd.uid b/addons/project-statistics/nodes/MiscView.gd.uid new file mode 100644 index 00000000..cd3fa6f4 --- /dev/null +++ b/addons/project-statistics/nodes/MiscView.gd.uid @@ -0,0 +1 @@ +uid://sf7aetq7v1ur diff --git a/addons/project-statistics/nodes/MiscView.tscn b/addons/project-statistics/nodes/MiscView.tscn index 55f31441..5866beed 100644 --- a/addons/project-statistics/nodes/MiscView.tscn +++ b/addons/project-statistics/nodes/MiscView.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=3 format=3 uid="uid://ddli62n5pmlvn"] -[ext_resource type="Script" path="res://addons/project-statistics/nodes/MiscView.gd" id="1"] +[ext_resource type="Script" uid="uid://sf7aetq7v1ur" path="res://addons/project-statistics/nodes/MiscView.gd" id="1"] [ext_resource type="PackedScene" uid="uid://b7wvldetyse4y" path="res://addons/project-statistics/nodes/ScriptsView.tscn" id="2"] [node name="MiscView" instance=ExtResource("2")] diff --git a/addons/project-statistics/nodes/Overview.gd.uid b/addons/project-statistics/nodes/Overview.gd.uid new file mode 100644 index 00000000..b77282d4 --- /dev/null +++ b/addons/project-statistics/nodes/Overview.gd.uid @@ -0,0 +1 @@ +uid://b7uivqbd7jt14 diff --git a/addons/project-statistics/nodes/Overview.tscn b/addons/project-statistics/nodes/Overview.tscn index 2dfa8bad..b83d652d 100644 --- a/addons/project-statistics/nodes/Overview.tscn +++ b/addons/project-statistics/nodes/Overview.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=3 format=3 uid="uid://03754kkjn2ue"] [ext_resource type="PackedScene" uid="uid://by7ltrt0iq35i" path="res://addons/project-statistics/nodes/charts/PieGraph.tscn" id="1"] -[ext_resource type="Script" path="res://addons/project-statistics/nodes/Overview.gd" id="3"] +[ext_resource type="Script" uid="uid://b7uivqbd7jt14" path="res://addons/project-statistics/nodes/Overview.gd" id="3"] [node name="Overview" type="MarginContainer"] anchors_preset = 15 diff --git a/addons/project-statistics/nodes/PanelContainer.gd.uid b/addons/project-statistics/nodes/PanelContainer.gd.uid new file mode 100644 index 00000000..0cbe08bc --- /dev/null +++ b/addons/project-statistics/nodes/PanelContainer.gd.uid @@ -0,0 +1 @@ +uid://51iguai2fy1x diff --git a/addons/project-statistics/nodes/ResourcesView.gd.uid b/addons/project-statistics/nodes/ResourcesView.gd.uid new file mode 100644 index 00000000..bc95e12a --- /dev/null +++ b/addons/project-statistics/nodes/ResourcesView.gd.uid @@ -0,0 +1 @@ +uid://b0axytju10cdf diff --git a/addons/project-statistics/nodes/ResourcesView.tscn b/addons/project-statistics/nodes/ResourcesView.tscn index 1dc6fe71..84c47cfd 100644 --- a/addons/project-statistics/nodes/ResourcesView.tscn +++ b/addons/project-statistics/nodes/ResourcesView.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=3 format=3 uid="uid://vcehrmbfpknm"] -[ext_resource type="Script" path="res://addons/project-statistics/nodes/ResourcesView.gd" id="1"] +[ext_resource type="Script" uid="uid://b0axytju10cdf" path="res://addons/project-statistics/nodes/ResourcesView.gd" id="1"] [ext_resource type="PackedScene" uid="uid://b7wvldetyse4y" path="res://addons/project-statistics/nodes/ScriptsView.tscn" id="2"] [node name="ResourcesView" instance=ExtResource("2")] diff --git a/addons/project-statistics/nodes/ScenesView.gd.uid b/addons/project-statistics/nodes/ScenesView.gd.uid new file mode 100644 index 00000000..856376c3 --- /dev/null +++ b/addons/project-statistics/nodes/ScenesView.gd.uid @@ -0,0 +1 @@ +uid://bj5xqqinnsfk1 diff --git a/addons/project-statistics/nodes/ScenesView.tscn b/addons/project-statistics/nodes/ScenesView.tscn index 141e4c27..a4a37550 100644 --- a/addons/project-statistics/nodes/ScenesView.tscn +++ b/addons/project-statistics/nodes/ScenesView.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=2 format=3 uid="uid://dfmtk6v2uv63o"] -[ext_resource type="Script" path="res://addons/project-statistics/nodes/ScenesView.gd" id="1"] +[ext_resource type="Script" uid="uid://bj5xqqinnsfk1" path="res://addons/project-statistics/nodes/ScenesView.gd" id="1"] [node name="ScenesView" type="MarginContainer"] anchors_preset = 15 diff --git a/addons/project-statistics/nodes/ScriptsView.gd.uid b/addons/project-statistics/nodes/ScriptsView.gd.uid new file mode 100644 index 00000000..913a9358 --- /dev/null +++ b/addons/project-statistics/nodes/ScriptsView.gd.uid @@ -0,0 +1 @@ +uid://bt7goako1t4oe diff --git a/addons/project-statistics/nodes/ScriptsView.tscn b/addons/project-statistics/nodes/ScriptsView.tscn index b62c3266..343071e4 100644 --- a/addons/project-statistics/nodes/ScriptsView.tscn +++ b/addons/project-statistics/nodes/ScriptsView.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=3 format=3 uid="uid://b7wvldetyse4y"] [ext_resource type="PackedScene" uid="uid://by7ltrt0iq35i" path="res://addons/project-statistics/nodes/charts/PieGraph.tscn" id="1"] -[ext_resource type="Script" path="res://addons/project-statistics/nodes/ScriptsView.gd" id="2"] +[ext_resource type="Script" uid="uid://bt7goako1t4oe" path="res://addons/project-statistics/nodes/ScriptsView.gd" id="2"] [node name="ScriptsView" type="MarginContainer"] anchor_right = 1.0 diff --git a/addons/project-statistics/nodes/StatisticsPreview.gd.uid b/addons/project-statistics/nodes/StatisticsPreview.gd.uid new file mode 100644 index 00000000..382fc942 --- /dev/null +++ b/addons/project-statistics/nodes/StatisticsPreview.gd.uid @@ -0,0 +1 @@ +uid://b0upy4h2k6n7j diff --git a/addons/project-statistics/nodes/StatisticsPreview.tscn b/addons/project-statistics/nodes/StatisticsPreview.tscn index a9a17278..e4a1821d 100644 --- a/addons/project-statistics/nodes/StatisticsPreview.tscn +++ b/addons/project-statistics/nodes/StatisticsPreview.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=7 format=3 uid="uid://bfqwgtk1grusq"] -[ext_resource type="Script" path="res://addons/project-statistics/nodes/StatisticsPreview.gd" id="1"] +[ext_resource type="Script" uid="uid://b0upy4h2k6n7j" path="res://addons/project-statistics/nodes/StatisticsPreview.gd" id="1"] [ext_resource type="PackedScene" uid="uid://03754kkjn2ue" path="res://addons/project-statistics/nodes/Overview.tscn" id="2"] [ext_resource type="PackedScene" uid="uid://dfmtk6v2uv63o" path="res://addons/project-statistics/nodes/ScenesView.tscn" id="3"] [ext_resource type="PackedScene" uid="uid://vcehrmbfpknm" path="res://addons/project-statistics/nodes/ResourcesView.tscn" id="4"] diff --git a/addons/project-statistics/nodes/StatisticsView.gd.uid b/addons/project-statistics/nodes/StatisticsView.gd.uid new file mode 100644 index 00000000..b97d3f02 --- /dev/null +++ b/addons/project-statistics/nodes/StatisticsView.gd.uid @@ -0,0 +1 @@ +uid://1dilpfcq8usx diff --git a/addons/project-statistics/nodes/TreeView.gd.uid b/addons/project-statistics/nodes/TreeView.gd.uid new file mode 100644 index 00000000..4caf7f43 --- /dev/null +++ b/addons/project-statistics/nodes/TreeView.gd.uid @@ -0,0 +1 @@ +uid://jvq50i5o0ntn diff --git a/addons/project-statistics/nodes/charts/ChartData.gd.uid b/addons/project-statistics/nodes/charts/ChartData.gd.uid new file mode 100644 index 00000000..e494a15f --- /dev/null +++ b/addons/project-statistics/nodes/charts/ChartData.gd.uid @@ -0,0 +1 @@ +uid://du5q4uq3rlfd7 diff --git a/addons/project-statistics/nodes/charts/PieChart.gd.uid b/addons/project-statistics/nodes/charts/PieChart.gd.uid new file mode 100644 index 00000000..136b74ae --- /dev/null +++ b/addons/project-statistics/nodes/charts/PieChart.gd.uid @@ -0,0 +1 @@ +uid://gv3scm0e3ovy diff --git a/addons/project-statistics/nodes/charts/PieGraph.gd.uid b/addons/project-statistics/nodes/charts/PieGraph.gd.uid new file mode 100644 index 00000000..784b1be2 --- /dev/null +++ b/addons/project-statistics/nodes/charts/PieGraph.gd.uid @@ -0,0 +1 @@ +uid://bv0uk72s0tryx diff --git a/addons/project-statistics/nodes/charts/PieGraph.tscn b/addons/project-statistics/nodes/charts/PieGraph.tscn index 5758d7b8..97622004 100644 --- a/addons/project-statistics/nodes/charts/PieGraph.tscn +++ b/addons/project-statistics/nodes/charts/PieGraph.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=3 format=3 uid="uid://by7ltrt0iq35i"] -[ext_resource type="Script" path="res://addons/project-statistics/nodes/charts/PieChart.gd" id="1"] -[ext_resource type="Script" path="res://addons/project-statistics/nodes/charts/PieGraph.gd" id="2"] +[ext_resource type="Script" uid="uid://gv3scm0e3ovy" path="res://addons/project-statistics/nodes/charts/PieChart.gd" id="1"] +[ext_resource type="Script" uid="uid://bv0uk72s0tryx" path="res://addons/project-statistics/nodes/charts/PieGraph.gd" id="2"] [node name="PieGraph" type="VSplitContainer"] anchors_preset = 15 diff --git a/addons/project-statistics/plugin.gd.uid b/addons/project-statistics/plugin.gd.uid new file mode 100644 index 00000000..853b6425 --- /dev/null +++ b/addons/project-statistics/plugin.gd.uid @@ -0,0 +1 @@ +uid://dt0jbri4rmrwp diff --git a/addons/property-inspector/inspector.gd.uid b/addons/property-inspector/inspector.gd.uid new file mode 100644 index 00000000..17faae3b --- /dev/null +++ b/addons/property-inspector/inspector.gd.uid @@ -0,0 +1 @@ +uid://d2e2aoafrkk6k diff --git a/addons/property-inspector/plugin.gd.uid b/addons/property-inspector/plugin.gd.uid new file mode 100644 index 00000000..fd5f63d1 --- /dev/null +++ b/addons/property-inspector/plugin.gd.uid @@ -0,0 +1 @@ +uid://dpsr2s7m7gjed diff --git a/addons/property-inspector/pro_animation_sprite2d/pro_animated_sprite.gd.uid b/addons/property-inspector/pro_animation_sprite2d/pro_animated_sprite.gd.uid new file mode 100644 index 00000000..cf91658d --- /dev/null +++ b/addons/property-inspector/pro_animation_sprite2d/pro_animated_sprite.gd.uid @@ -0,0 +1 @@ +uid://cphfob11f7atx diff --git a/addons/property-inspector/pro_animation_sprite2d/pro_animated_sprite.tscn b/addons/property-inspector/pro_animation_sprite2d/pro_animated_sprite.tscn index 6b774b46..76e57622 100644 --- a/addons/property-inspector/pro_animation_sprite2d/pro_animated_sprite.tscn +++ b/addons/property-inspector/pro_animation_sprite2d/pro_animated_sprite.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=2 format=3 uid="uid://b50n0hvs4yh75"] -[ext_resource type="Script" path="res://addons/property-inspector/pro_animation_sprite2d/pro_animated_sprite.gd" id="1_21eda"] +[ext_resource type="Script" uid="uid://cphfob11f7atx" path="res://addons/property-inspector/pro_animation_sprite2d/pro_animated_sprite.gd" id="1_21eda"] [node name="AutoplayAnimatedSprite" type="AnimatedSprite2D"] position = Vector2(-1, -1) diff --git a/addons/property-inspector/pro_animation_sprite2d/pro_animation_action_editor.gd.uid b/addons/property-inspector/pro_animation_sprite2d/pro_animation_action_editor.gd.uid new file mode 100644 index 00000000..eda5d98a --- /dev/null +++ b/addons/property-inspector/pro_animation_sprite2d/pro_animation_action_editor.gd.uid @@ -0,0 +1 @@ +uid://dhovxqa0p3km7 diff --git a/addons/property-inspector/pro_animation_sprite2d/pro_animation_move_editor.gd.uid b/addons/property-inspector/pro_animation_sprite2d/pro_animation_move_editor.gd.uid new file mode 100644 index 00000000..36e2693c --- /dev/null +++ b/addons/property-inspector/pro_animation_sprite2d/pro_animation_move_editor.gd.uid @@ -0,0 +1 @@ +uid://c5005ofa8f1qj diff --git a/addons/property-inspector/pro_animation_sprite2d/state_action_res.gd.uid b/addons/property-inspector/pro_animation_sprite2d/state_action_res.gd.uid new file mode 100644 index 00000000..a572eb68 --- /dev/null +++ b/addons/property-inspector/pro_animation_sprite2d/state_action_res.gd.uid @@ -0,0 +1 @@ +uid://7r7kntbe4yl4 diff --git a/addons/property-inspector/pro_animation_sprite2d/state_move_res.gd.uid b/addons/property-inspector/pro_animation_sprite2d/state_move_res.gd.uid new file mode 100644 index 00000000..315a7172 --- /dev/null +++ b/addons/property-inspector/pro_animation_sprite2d/state_move_res.gd.uid @@ -0,0 +1 @@ +uid://dn6wag0mm1io7 diff --git a/addons/resources_spreadsheet_view/editor_color_setter.gd.uid b/addons/resources_spreadsheet_view/editor_color_setter.gd.uid new file mode 100644 index 00000000..435d452c --- /dev/null +++ b/addons/resources_spreadsheet_view/editor_color_setter.gd.uid @@ -0,0 +1 @@ +uid://b5t4iivnniojm diff --git a/addons/resources_spreadsheet_view/editor_icon_button.gd.uid b/addons/resources_spreadsheet_view/editor_icon_button.gd.uid new file mode 100644 index 00000000..56f0bc40 --- /dev/null +++ b/addons/resources_spreadsheet_view/editor_icon_button.gd.uid @@ -0,0 +1 @@ +uid://bieypbdiqrkl7 diff --git a/addons/resources_spreadsheet_view/editor_view.gd.uid b/addons/resources_spreadsheet_view/editor_view.gd.uid new file mode 100644 index 00000000..c8efe1f4 --- /dev/null +++ b/addons/resources_spreadsheet_view/editor_view.gd.uid @@ -0,0 +1 @@ +uid://chk2qt43vioj2 diff --git a/addons/resources_spreadsheet_view/editor_view.tscn b/addons/resources_spreadsheet_view/editor_view.tscn index ba72f7d5..dbd73653 100644 --- a/addons/resources_spreadsheet_view/editor_view.tscn +++ b/addons/resources_spreadsheet_view/editor_view.tscn @@ -1,12 +1,12 @@ [gd_scene load_steps=30 format=3 uid="uid://tmleonv20aqk"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/editor_view.gd" id="1_wfx75"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/editor_color_setter.gd" id="2_t2s7k"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/editor_icon_button.gd" id="3_7ja2l"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/main_screen/recent_paths.gd" id="4_umoob"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/main_screen/expression_textfield.gd" id="5_faq75"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/main_screen/table_pages.gd" id="5_ka2yn"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/main_screen/column_header_manager.gd" id="6_emnmd"] +[ext_resource type="Script" uid="uid://chk2qt43vioj2" path="res://addons/resources_spreadsheet_view/editor_view.gd" id="1_wfx75"] +[ext_resource type="Script" uid="uid://b5t4iivnniojm" path="res://addons/resources_spreadsheet_view/editor_color_setter.gd" id="2_t2s7k"] +[ext_resource type="Script" uid="uid://bieypbdiqrkl7" path="res://addons/resources_spreadsheet_view/editor_icon_button.gd" id="3_7ja2l"] +[ext_resource type="Script" uid="uid://cjqpnb4ndjr3f" path="res://addons/resources_spreadsheet_view/main_screen/recent_paths.gd" id="4_umoob"] +[ext_resource type="Script" uid="uid://bg73cs7aikl4g" path="res://addons/resources_spreadsheet_view/main_screen/expression_textfield.gd" id="5_faq75"] +[ext_resource type="Script" uid="uid://45onn4jw5fcc" path="res://addons/resources_spreadsheet_view/main_screen/table_pages.gd" id="5_ka2yn"] +[ext_resource type="Script" uid="uid://dhq5w4cn13d08" path="res://addons/resources_spreadsheet_view/main_screen/column_header_manager.gd" id="6_emnmd"] [ext_resource type="PackedScene" uid="uid://d1s6oihqedvo5" path="res://addons/resources_spreadsheet_view/main_screen/table_header.tscn" id="7_3dx0v"] [ext_resource type="PackedScene" uid="uid://ddqak780cwwfj" path="res://addons/resources_spreadsheet_view/typed_editors/dock_enum_array.tscn" id="8_234wn"] [ext_resource type="PackedScene" uid="uid://c3a2cip8ffccv" path="res://addons/resources_spreadsheet_view/typed_editors/dock_array.tscn" id="9_nts08"] @@ -15,17 +15,17 @@ [ext_resource type="PackedScene" uid="uid://rww3gpl052bn" path="res://addons/resources_spreadsheet_view/typed_editors/dock_texture.tscn" id="12_4kr6q"] [ext_resource type="PackedScene" uid="uid://dhunxgcae6h1" path="res://addons/resources_spreadsheet_view/settings_grid.tscn" id="13_as1sh"] [ext_resource type="PackedScene" uid="uid://p6x03dbvhnqw" path="res://addons/resources_spreadsheet_view/typed_editors/dock_dict.tscn" id="13_il556"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/main_screen/input_handler.gd" id="14_2t57a"] +[ext_resource type="Script" uid="uid://vdk2jc4putik" path="res://addons/resources_spreadsheet_view/main_screen/input_handler.gd" id="14_2t57a"] [ext_resource type="PackedScene" uid="uid://b413igx28kkvb" path="res://addons/resources_spreadsheet_view/import_export/import_export_dialog.tscn" id="14_3p12b"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/main_screen/selection_manager.gd" id="15_mx6qn"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/typed_cells/cell_editor_enum_array.gd" id="16_p7n52"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/typed_cells/cell_editor_array.gd" id="17_sofdw"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/typed_cells/cell_editor_color.gd" id="18_oeewr"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/typed_cells/cell_editor_bool.gd" id="19_7x44x"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/typed_cells/cell_editor_dict.gd" id="19_oeuko"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/typed_cells/cell_editor_enum.gd" id="20_swsbn"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/typed_cells/cell_editor_resource.gd" id="21_58wf8"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/typed_cells/cell_editor_string.gd" id="22_bni8r"] +[ext_resource type="Script" uid="uid://ds4l1utan1l8s" path="res://addons/resources_spreadsheet_view/main_screen/selection_manager.gd" id="15_mx6qn"] +[ext_resource type="Script" uid="uid://b8ix0h7ovup51" path="res://addons/resources_spreadsheet_view/typed_cells/cell_editor_enum_array.gd" id="16_p7n52"] +[ext_resource type="Script" uid="uid://uuyxn25mxexq" path="res://addons/resources_spreadsheet_view/typed_cells/cell_editor_array.gd" id="17_sofdw"] +[ext_resource type="Script" uid="uid://b18omb27hdrj3" path="res://addons/resources_spreadsheet_view/typed_cells/cell_editor_color.gd" id="18_oeewr"] +[ext_resource type="Script" uid="uid://ctf41vyt6s4r4" path="res://addons/resources_spreadsheet_view/typed_cells/cell_editor_bool.gd" id="19_7x44x"] +[ext_resource type="Script" uid="uid://v50y6wt7hd43" path="res://addons/resources_spreadsheet_view/typed_cells/cell_editor_dict.gd" id="19_oeuko"] +[ext_resource type="Script" uid="uid://qmampsi0ga2q" path="res://addons/resources_spreadsheet_view/typed_cells/cell_editor_enum.gd" id="20_swsbn"] +[ext_resource type="Script" uid="uid://7budnbdhma43" path="res://addons/resources_spreadsheet_view/typed_cells/cell_editor_resource.gd" id="21_58wf8"] +[ext_resource type="Script" uid="uid://gs4sjx7kjonf" path="res://addons/resources_spreadsheet_view/typed_cells/cell_editor_string.gd" id="22_bni8r"] [ext_resource type="PackedScene" uid="uid://b51hnttsie7k5" path="res://addons/resources_spreadsheet_view/main_screen/selection_actions.tscn" id="23_m53sx"] [sub_resource type="Gradient" id="Gradient_8kp6w"] diff --git a/addons/resources_spreadsheet_view/import_export/formats_edit/edit_base.gd.uid b/addons/resources_spreadsheet_view/import_export/formats_edit/edit_base.gd.uid new file mode 100644 index 00000000..0590f097 --- /dev/null +++ b/addons/resources_spreadsheet_view/import_export/formats_edit/edit_base.gd.uid @@ -0,0 +1 @@ +uid://cxptayut51pl5 diff --git a/addons/resources_spreadsheet_view/import_export/formats_edit/edit_csv.gd.uid b/addons/resources_spreadsheet_view/import_export/formats_edit/edit_csv.gd.uid new file mode 100644 index 00000000..c663f77f --- /dev/null +++ b/addons/resources_spreadsheet_view/import_export/formats_edit/edit_csv.gd.uid @@ -0,0 +1 @@ +uid://ts7qxsfn3toq diff --git a/addons/resources_spreadsheet_view/import_export/formats_edit/edit_tres.gd.uid b/addons/resources_spreadsheet_view/import_export/formats_edit/edit_tres.gd.uid new file mode 100644 index 00000000..ba235d01 --- /dev/null +++ b/addons/resources_spreadsheet_view/import_export/formats_edit/edit_tres.gd.uid @@ -0,0 +1 @@ +uid://ctnamuotguyje diff --git a/addons/resources_spreadsheet_view/import_export/formats_export/export_csv.gd.uid b/addons/resources_spreadsheet_view/import_export/formats_export/export_csv.gd.uid new file mode 100644 index 00000000..16780a55 --- /dev/null +++ b/addons/resources_spreadsheet_view/import_export/formats_export/export_csv.gd.uid @@ -0,0 +1 @@ +uid://bjdyda7fb6bk4 diff --git a/addons/resources_spreadsheet_view/import_export/formats_import/import_csv.gd.uid b/addons/resources_spreadsheet_view/import_export/formats_import/import_csv.gd.uid new file mode 100644 index 00000000..57efeaa0 --- /dev/null +++ b/addons/resources_spreadsheet_view/import_export/formats_import/import_csv.gd.uid @@ -0,0 +1 @@ +uid://lnc88q6gooui diff --git a/addons/resources_spreadsheet_view/import_export/import_export_dialog.gd.uid b/addons/resources_spreadsheet_view/import_export/import_export_dialog.gd.uid new file mode 100644 index 00000000..73cd79f5 --- /dev/null +++ b/addons/resources_spreadsheet_view/import_export/import_export_dialog.gd.uid @@ -0,0 +1 @@ +uid://c4a6i1ncdpaox diff --git a/addons/resources_spreadsheet_view/import_export/import_export_dialog.tscn b/addons/resources_spreadsheet_view/import_export/import_export_dialog.tscn index d3de473f..a44beb97 100644 --- a/addons/resources_spreadsheet_view/import_export/import_export_dialog.tscn +++ b/addons/resources_spreadsheet_view/import_export/import_export_dialog.tscn @@ -1,8 +1,8 @@ [gd_scene load_steps=7 format=3 uid="uid://b413igx28kkvb"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/import_export/import_export_dialog.gd" id="1"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/import_export/formats_export/export_csv.gd" id="2_33c6s"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/import_export/formats_import/import_csv.gd" id="2_fxayt"] +[ext_resource type="Script" uid="uid://c4a6i1ncdpaox" path="res://addons/resources_spreadsheet_view/import_export/import_export_dialog.gd" id="1"] +[ext_resource type="Script" uid="uid://bjdyda7fb6bk4" path="res://addons/resources_spreadsheet_view/import_export/formats_export/export_csv.gd" id="2_33c6s"] +[ext_resource type="Script" uid="uid://lnc88q6gooui" path="res://addons/resources_spreadsheet_view/import_export/formats_import/import_csv.gd" id="2_fxayt"] [ext_resource type="PackedScene" uid="uid://b8llymigbprh6" path="res://addons/resources_spreadsheet_view/import_export/property_list_item.tscn" id="2_xfhmf"] [ext_resource type="PackedScene" uid="uid://ckhf3bqy2rqjr" path="res://addons/resources_spreadsheet_view/import_export/import_export_enum_format.tscn" id="4"] diff --git a/addons/resources_spreadsheet_view/import_export/import_export_enum_format.gd.uid b/addons/resources_spreadsheet_view/import_export/import_export_enum_format.gd.uid new file mode 100644 index 00000000..f2802d0d --- /dev/null +++ b/addons/resources_spreadsheet_view/import_export/import_export_enum_format.gd.uid @@ -0,0 +1 @@ +uid://bmsuwt0hpgrc3 diff --git a/addons/resources_spreadsheet_view/import_export/import_export_enum_format.tscn b/addons/resources_spreadsheet_view/import_export/import_export_enum_format.tscn index 39ec94a2..72775b5c 100644 --- a/addons/resources_spreadsheet_view/import_export/import_export_enum_format.tscn +++ b/addons/resources_spreadsheet_view/import_export/import_export_enum_format.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=2 format=3 uid="uid://ckhf3bqy2rqjr"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/import_export/import_export_enum_format.gd" id="1"] +[ext_resource type="Script" uid="uid://bmsuwt0hpgrc3" path="res://addons/resources_spreadsheet_view/import_export/import_export_enum_format.gd" id="1"] [node name="EnumFormat" type="GridContainer"] columns = 2 diff --git a/addons/resources_spreadsheet_view/import_export/property_list_item.gd.uid b/addons/resources_spreadsheet_view/import_export/property_list_item.gd.uid new file mode 100644 index 00000000..0dfc08eb --- /dev/null +++ b/addons/resources_spreadsheet_view/import_export/property_list_item.gd.uid @@ -0,0 +1 @@ +uid://dfr2np3dvc1af diff --git a/addons/resources_spreadsheet_view/import_export/property_list_item.tscn b/addons/resources_spreadsheet_view/import_export/property_list_item.tscn index 75172d59..69cc49f8 100644 --- a/addons/resources_spreadsheet_view/import_export/property_list_item.tscn +++ b/addons/resources_spreadsheet_view/import_export/property_list_item.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=2 format=3 uid="uid://b8llymigbprh6"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/import_export/property_list_item.gd" id="1"] +[ext_resource type="Script" uid="uid://dfr2np3dvc1af" path="res://addons/resources_spreadsheet_view/import_export/property_list_item.gd" id="1"] [node name="Entry" type="HBoxContainer"] script = ExtResource("1") diff --git a/addons/resources_spreadsheet_view/import_export/spreadsheet_import.gd.uid b/addons/resources_spreadsheet_view/import_export/spreadsheet_import.gd.uid new file mode 100644 index 00000000..1b5a2653 --- /dev/null +++ b/addons/resources_spreadsheet_view/import_export/spreadsheet_import.gd.uid @@ -0,0 +1 @@ +uid://b2qhxxox4vs7h diff --git a/addons/resources_spreadsheet_view/main_screen/column_header_manager.gd.uid b/addons/resources_spreadsheet_view/main_screen/column_header_manager.gd.uid new file mode 100644 index 00000000..48ed6be0 --- /dev/null +++ b/addons/resources_spreadsheet_view/main_screen/column_header_manager.gd.uid @@ -0,0 +1 @@ +uid://dhq5w4cn13d08 diff --git a/addons/resources_spreadsheet_view/main_screen/expression_textfield.gd.uid b/addons/resources_spreadsheet_view/main_screen/expression_textfield.gd.uid new file mode 100644 index 00000000..ea5be018 --- /dev/null +++ b/addons/resources_spreadsheet_view/main_screen/expression_textfield.gd.uid @@ -0,0 +1 @@ +uid://bg73cs7aikl4g diff --git a/addons/resources_spreadsheet_view/main_screen/input_handler.gd.uid b/addons/resources_spreadsheet_view/main_screen/input_handler.gd.uid new file mode 100644 index 00000000..7ac11faa --- /dev/null +++ b/addons/resources_spreadsheet_view/main_screen/input_handler.gd.uid @@ -0,0 +1 @@ +uid://vdk2jc4putik diff --git a/addons/resources_spreadsheet_view/main_screen/recent_paths.gd.uid b/addons/resources_spreadsheet_view/main_screen/recent_paths.gd.uid new file mode 100644 index 00000000..90b93682 --- /dev/null +++ b/addons/resources_spreadsheet_view/main_screen/recent_paths.gd.uid @@ -0,0 +1 @@ +uid://cjqpnb4ndjr3f diff --git a/addons/resources_spreadsheet_view/main_screen/selection_actions.gd.uid b/addons/resources_spreadsheet_view/main_screen/selection_actions.gd.uid new file mode 100644 index 00000000..961c713b --- /dev/null +++ b/addons/resources_spreadsheet_view/main_screen/selection_actions.gd.uid @@ -0,0 +1 @@ +uid://c6ynk6v80rtjp diff --git a/addons/resources_spreadsheet_view/main_screen/selection_actions.tscn b/addons/resources_spreadsheet_view/main_screen/selection_actions.tscn index f9350ab8..df3c918c 100644 --- a/addons/resources_spreadsheet_view/main_screen/selection_actions.tscn +++ b/addons/resources_spreadsheet_view/main_screen/selection_actions.tscn @@ -1,10 +1,10 @@ [gd_scene load_steps=7 format=3 uid="uid://b51hnttsie7k5"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/editor_icon_button.gd" id="1"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/main_screen/selection_actions.gd" id="1_qv6ov"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/editor_color_setter.gd" id="2_a4ihj"] +[ext_resource type="Script" uid="uid://bieypbdiqrkl7" path="res://addons/resources_spreadsheet_view/editor_icon_button.gd" id="1"] +[ext_resource type="Script" uid="uid://c6ynk6v80rtjp" path="res://addons/resources_spreadsheet_view/main_screen/selection_actions.gd" id="1_qv6ov"] +[ext_resource type="Script" uid="uid://b5t4iivnniojm" path="res://addons/resources_spreadsheet_view/editor_color_setter.gd" id="2_a4ihj"] -[sub_resource type="Image" id="Image_ikq5t"] +[sub_resource type="Image" id="Image_qja2v"] data = { "data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), "format": "LumAlpha8", @@ -14,7 +14,7 @@ data = { } [sub_resource type="ImageTexture" id="2"] -image = SubResource("Image_ikq5t") +image = SubResource("Image_qja2v") [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ydxuk"] content_margin_left = 4.0 diff --git a/addons/resources_spreadsheet_view/main_screen/selection_manager.gd.uid b/addons/resources_spreadsheet_view/main_screen/selection_manager.gd.uid new file mode 100644 index 00000000..9f6308f8 --- /dev/null +++ b/addons/resources_spreadsheet_view/main_screen/selection_manager.gd.uid @@ -0,0 +1 @@ +uid://ds4l1utan1l8s diff --git a/addons/resources_spreadsheet_view/main_screen/table_header.gd.uid b/addons/resources_spreadsheet_view/main_screen/table_header.gd.uid new file mode 100644 index 00000000..fe8be6d0 --- /dev/null +++ b/addons/resources_spreadsheet_view/main_screen/table_header.gd.uid @@ -0,0 +1 @@ +uid://bogbww5cjotjj diff --git a/addons/resources_spreadsheet_view/main_screen/table_header.tscn b/addons/resources_spreadsheet_view/main_screen/table_header.tscn index bd7fbb71..b13f8311 100644 --- a/addons/resources_spreadsheet_view/main_screen/table_header.tscn +++ b/addons/resources_spreadsheet_view/main_screen/table_header.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=3 format=3 uid="uid://d1s6oihqedvo5"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/main_screen/table_header.gd" id="1_5fd1m"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/editor_icon_button.gd" id="2_0ymob"] +[ext_resource type="Script" uid="uid://bogbww5cjotjj" path="res://addons/resources_spreadsheet_view/main_screen/table_header.gd" id="1_5fd1m"] +[ext_resource type="Script" uid="uid://bieypbdiqrkl7" path="res://addons/resources_spreadsheet_view/editor_icon_button.gd" id="2_0ymob"] [node name="Header" type="HBoxContainer"] offset_right = 179.0 diff --git a/addons/resources_spreadsheet_view/main_screen/table_pages.gd.uid b/addons/resources_spreadsheet_view/main_screen/table_pages.gd.uid new file mode 100644 index 00000000..4bc88119 --- /dev/null +++ b/addons/resources_spreadsheet_view/main_screen/table_pages.gd.uid @@ -0,0 +1 @@ +uid://45onn4jw5fcc diff --git a/addons/resources_spreadsheet_view/plugin.gd.uid b/addons/resources_spreadsheet_view/plugin.gd.uid new file mode 100644 index 00000000..3251f211 --- /dev/null +++ b/addons/resources_spreadsheet_view/plugin.gd.uid @@ -0,0 +1 @@ +uid://cr8xufrtccweq diff --git a/addons/resources_spreadsheet_view/settings_grid.gd.uid b/addons/resources_spreadsheet_view/settings_grid.gd.uid new file mode 100644 index 00000000..e5a5f4c0 --- /dev/null +++ b/addons/resources_spreadsheet_view/settings_grid.gd.uid @@ -0,0 +1 @@ +uid://crq1fhguvm2jn diff --git a/addons/resources_spreadsheet_view/settings_grid.tscn b/addons/resources_spreadsheet_view/settings_grid.tscn index 61ffba83..033f7ac3 100644 --- a/addons/resources_spreadsheet_view/settings_grid.tscn +++ b/addons/resources_spreadsheet_view/settings_grid.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=2 format=3 uid="uid://dhunxgcae6h1"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/settings_grid.gd" id="1_s8s2f"] +[ext_resource type="Script" uid="uid://crq1fhguvm2jn" path="res://addons/resources_spreadsheet_view/settings_grid.gd" id="1_s8s2f"] [node name="Settings" type="ScrollContainer"] anchors_preset = 15 diff --git a/addons/resources_spreadsheet_view/text_editing_utils.gd.uid b/addons/resources_spreadsheet_view/text_editing_utils.gd.uid new file mode 100644 index 00000000..153652e0 --- /dev/null +++ b/addons/resources_spreadsheet_view/text_editing_utils.gd.uid @@ -0,0 +1 @@ +uid://djxmcap7qt7k1 diff --git a/addons/resources_spreadsheet_view/typed_cells/cell_editor.gd.uid b/addons/resources_spreadsheet_view/typed_cells/cell_editor.gd.uid new file mode 100644 index 00000000..796afcb9 --- /dev/null +++ b/addons/resources_spreadsheet_view/typed_cells/cell_editor.gd.uid @@ -0,0 +1 @@ +uid://cx3yvtgcbk2dh diff --git a/addons/resources_spreadsheet_view/typed_cells/cell_editor_array.gd.uid b/addons/resources_spreadsheet_view/typed_cells/cell_editor_array.gd.uid new file mode 100644 index 00000000..572ac346 --- /dev/null +++ b/addons/resources_spreadsheet_view/typed_cells/cell_editor_array.gd.uid @@ -0,0 +1 @@ +uid://uuyxn25mxexq diff --git a/addons/resources_spreadsheet_view/typed_cells/cell_editor_bool.gd.uid b/addons/resources_spreadsheet_view/typed_cells/cell_editor_bool.gd.uid new file mode 100644 index 00000000..a03015b0 --- /dev/null +++ b/addons/resources_spreadsheet_view/typed_cells/cell_editor_bool.gd.uid @@ -0,0 +1 @@ +uid://ctf41vyt6s4r4 diff --git a/addons/resources_spreadsheet_view/typed_cells/cell_editor_color.gd.uid b/addons/resources_spreadsheet_view/typed_cells/cell_editor_color.gd.uid new file mode 100644 index 00000000..2bc5bfa3 --- /dev/null +++ b/addons/resources_spreadsheet_view/typed_cells/cell_editor_color.gd.uid @@ -0,0 +1 @@ +uid://b18omb27hdrj3 diff --git a/addons/resources_spreadsheet_view/typed_cells/cell_editor_dict.gd.uid b/addons/resources_spreadsheet_view/typed_cells/cell_editor_dict.gd.uid new file mode 100644 index 00000000..d22d7b7e --- /dev/null +++ b/addons/resources_spreadsheet_view/typed_cells/cell_editor_dict.gd.uid @@ -0,0 +1 @@ +uid://v50y6wt7hd43 diff --git a/addons/resources_spreadsheet_view/typed_cells/cell_editor_enum.gd.uid b/addons/resources_spreadsheet_view/typed_cells/cell_editor_enum.gd.uid new file mode 100644 index 00000000..03d5d4b4 --- /dev/null +++ b/addons/resources_spreadsheet_view/typed_cells/cell_editor_enum.gd.uid @@ -0,0 +1 @@ +uid://qmampsi0ga2q diff --git a/addons/resources_spreadsheet_view/typed_cells/cell_editor_enum_array.gd.uid b/addons/resources_spreadsheet_view/typed_cells/cell_editor_enum_array.gd.uid new file mode 100644 index 00000000..87496e78 --- /dev/null +++ b/addons/resources_spreadsheet_view/typed_cells/cell_editor_enum_array.gd.uid @@ -0,0 +1 @@ +uid://b8ix0h7ovup51 diff --git a/addons/resources_spreadsheet_view/typed_cells/cell_editor_resource.gd.uid b/addons/resources_spreadsheet_view/typed_cells/cell_editor_resource.gd.uid new file mode 100644 index 00000000..0eac14a6 --- /dev/null +++ b/addons/resources_spreadsheet_view/typed_cells/cell_editor_resource.gd.uid @@ -0,0 +1 @@ +uid://7budnbdhma43 diff --git a/addons/resources_spreadsheet_view/typed_cells/cell_editor_string.gd.uid b/addons/resources_spreadsheet_view/typed_cells/cell_editor_string.gd.uid new file mode 100644 index 00000000..37dbffea --- /dev/null +++ b/addons/resources_spreadsheet_view/typed_cells/cell_editor_string.gd.uid @@ -0,0 +1 @@ +uid://gs4sjx7kjonf diff --git a/addons/resources_spreadsheet_view/typed_editors/dock_array.gd.uid b/addons/resources_spreadsheet_view/typed_editors/dock_array.gd.uid new file mode 100644 index 00000000..7d823b0c --- /dev/null +++ b/addons/resources_spreadsheet_view/typed_editors/dock_array.gd.uid @@ -0,0 +1 @@ +uid://27kguq1p55gs diff --git a/addons/resources_spreadsheet_view/typed_editors/dock_array.tscn b/addons/resources_spreadsheet_view/typed_editors/dock_array.tscn index 61958e70..fc04885c 100644 --- a/addons/resources_spreadsheet_view/typed_editors/dock_array.tscn +++ b/addons/resources_spreadsheet_view/typed_editors/dock_array.tscn @@ -1,9 +1,9 @@ [gd_scene load_steps=5 format=3 uid="uid://c3a2cip8ffccv"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/typed_editors/dock_array.gd" id="1"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/editor_icon_button.gd" id="2"] +[ext_resource type="Script" uid="uid://27kguq1p55gs" path="res://addons/resources_spreadsheet_view/typed_editors/dock_array.gd" id="1"] +[ext_resource type="Script" uid="uid://bieypbdiqrkl7" path="res://addons/resources_spreadsheet_view/editor_icon_button.gd" id="2"] -[sub_resource type="Image" id="Image_wsnda"] +[sub_resource type="Image" id="Image_4nb2y"] 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", @@ -13,7 +13,7 @@ data = { } [sub_resource type="ImageTexture" id="ImageTexture_3oshq"] -image = SubResource("Image_wsnda") +image = SubResource("Image_4nb2y") [node name="EditArray" type="VBoxContainer"] anchors_preset = 10 diff --git a/addons/resources_spreadsheet_view/typed_editors/dock_base.gd.uid b/addons/resources_spreadsheet_view/typed_editors/dock_base.gd.uid new file mode 100644 index 00000000..1e92d72c --- /dev/null +++ b/addons/resources_spreadsheet_view/typed_editors/dock_base.gd.uid @@ -0,0 +1 @@ +uid://bcb2o2cvcym1r diff --git a/addons/resources_spreadsheet_view/typed_editors/dock_color.gd.uid b/addons/resources_spreadsheet_view/typed_editors/dock_color.gd.uid new file mode 100644 index 00000000..4bde0f9a --- /dev/null +++ b/addons/resources_spreadsheet_view/typed_editors/dock_color.gd.uid @@ -0,0 +1 @@ +uid://kj6selmmbqk3 diff --git a/addons/resources_spreadsheet_view/typed_editors/dock_color.tscn b/addons/resources_spreadsheet_view/typed_editors/dock_color.tscn index d7774e16..93bdba7c 100644 --- a/addons/resources_spreadsheet_view/typed_editors/dock_color.tscn +++ b/addons/resources_spreadsheet_view/typed_editors/dock_color.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=2 format=3 uid="uid://b3a3bo6cfyh5t"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/typed_editors/dock_color.gd" id="1"] +[ext_resource type="Script" uid="uid://kj6selmmbqk3" path="res://addons/resources_spreadsheet_view/typed_editors/dock_color.gd" id="1"] [node name="EditColor" type="VBoxContainer"] offset_bottom = 131.0 diff --git a/addons/resources_spreadsheet_view/typed_editors/dock_dict.gd.uid b/addons/resources_spreadsheet_view/typed_editors/dock_dict.gd.uid new file mode 100644 index 00000000..1f310551 --- /dev/null +++ b/addons/resources_spreadsheet_view/typed_editors/dock_dict.gd.uid @@ -0,0 +1 @@ +uid://bf08a2liamrxp diff --git a/addons/resources_spreadsheet_view/typed_editors/dock_dict.tscn b/addons/resources_spreadsheet_view/typed_editors/dock_dict.tscn index 9fa13c35..40546385 100644 --- a/addons/resources_spreadsheet_view/typed_editors/dock_dict.tscn +++ b/addons/resources_spreadsheet_view/typed_editors/dock_dict.tscn @@ -1,9 +1,9 @@ [gd_scene load_steps=5 format=3 uid="uid://p6x03dbvhnqw"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/typed_editors/dock_dict.gd" id="1_2yivi"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/editor_icon_button.gd" id="2_yck0k"] +[ext_resource type="Script" uid="uid://bf08a2liamrxp" path="res://addons/resources_spreadsheet_view/typed_editors/dock_dict.gd" id="1_2yivi"] +[ext_resource type="Script" uid="uid://bieypbdiqrkl7" path="res://addons/resources_spreadsheet_view/editor_icon_button.gd" id="2_yck0k"] -[sub_resource type="Image" id="Image_hekk1"] +[sub_resource type="Image" id="Image_a1yjd"] 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", @@ -13,7 +13,7 @@ data = { } [sub_resource type="ImageTexture" id="ImageTexture_3oshq"] -image = SubResource("Image_hekk1") +image = SubResource("Image_a1yjd") [node name="EditArray" type="VBoxContainer"] anchors_preset = 10 diff --git a/addons/resources_spreadsheet_view/typed_editors/dock_enum_array.gd.uid b/addons/resources_spreadsheet_view/typed_editors/dock_enum_array.gd.uid new file mode 100644 index 00000000..fe0e3e6a --- /dev/null +++ b/addons/resources_spreadsheet_view/typed_editors/dock_enum_array.gd.uid @@ -0,0 +1 @@ +uid://bvvh37oefqe7q diff --git a/addons/resources_spreadsheet_view/typed_editors/dock_enum_array.tscn b/addons/resources_spreadsheet_view/typed_editors/dock_enum_array.tscn index d2534ebb..adaca7f5 100644 --- a/addons/resources_spreadsheet_view/typed_editors/dock_enum_array.tscn +++ b/addons/resources_spreadsheet_view/typed_editors/dock_enum_array.tscn @@ -1,9 +1,9 @@ [gd_scene load_steps=5 format=3 uid="uid://ddqak780cwwfj"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/typed_editors/dock_enum_array.gd" id="1_n3flg"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/editor_icon_button.gd" id="2_mda1e"] +[ext_resource type="Script" uid="uid://bvvh37oefqe7q" path="res://addons/resources_spreadsheet_view/typed_editors/dock_enum_array.gd" id="1_n3flg"] +[ext_resource type="Script" uid="uid://bieypbdiqrkl7" path="res://addons/resources_spreadsheet_view/editor_icon_button.gd" id="2_mda1e"] -[sub_resource type="Image" id="Image_n47at"] +[sub_resource type="Image" id="Image_on1yq"] 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", @@ -13,7 +13,7 @@ data = { } [sub_resource type="ImageTexture" id="ImageTexture_3oshq"] -image = SubResource("Image_n47at") +image = SubResource("Image_on1yq") [node name="EditEnumArray" type="VBoxContainer"] anchors_preset = 10 diff --git a/addons/resources_spreadsheet_view/typed_editors/dock_number.gd.uid b/addons/resources_spreadsheet_view/typed_editors/dock_number.gd.uid new file mode 100644 index 00000000..c8b4690f --- /dev/null +++ b/addons/resources_spreadsheet_view/typed_editors/dock_number.gd.uid @@ -0,0 +1 @@ +uid://bnnr8tx3ya3hu diff --git a/addons/resources_spreadsheet_view/typed_editors/dock_number.tscn b/addons/resources_spreadsheet_view/typed_editors/dock_number.tscn index 86eea132..8fcd82a3 100644 --- a/addons/resources_spreadsheet_view/typed_editors/dock_number.tscn +++ b/addons/resources_spreadsheet_view/typed_editors/dock_number.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=2 format=3 uid="uid://gtbf7b0wptv"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/typed_editors/dock_number.gd" id="1"] +[ext_resource type="Script" uid="uid://bnnr8tx3ya3hu" path="res://addons/resources_spreadsheet_view/typed_editors/dock_number.gd" id="1"] [node name="EditNumber" type="VBoxContainer"] offset_right = 1141.0 diff --git a/addons/resources_spreadsheet_view/typed_editors/dock_texture.gd.uid b/addons/resources_spreadsheet_view/typed_editors/dock_texture.gd.uid new file mode 100644 index 00000000..7f149986 --- /dev/null +++ b/addons/resources_spreadsheet_view/typed_editors/dock_texture.gd.uid @@ -0,0 +1 @@ +uid://c0pruid67covp diff --git a/addons/resources_spreadsheet_view/typed_editors/dock_texture.tscn b/addons/resources_spreadsheet_view/typed_editors/dock_texture.tscn index fc2f411f..5d02a095 100644 --- a/addons/resources_spreadsheet_view/typed_editors/dock_texture.tscn +++ b/addons/resources_spreadsheet_view/typed_editors/dock_texture.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=3 format=3 uid="uid://rww3gpl052bn"] -[ext_resource type="Script" path="res://addons/resources_spreadsheet_view/typed_editors/dock_texture.gd" id="1"] +[ext_resource type="Script" uid="uid://c0pruid67covp" path="res://addons/resources_spreadsheet_view/typed_editors/dock_texture.gd" id="1"] [sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_h3mns"]