└── addons
└── dialogue_manager
├── DialogueManager.cs
├── DialogueManager.cs.uid
├── LICENSE
├── assets
├── icon.svg
├── icon.svg.import
├── responses_menu.svg
├── responses_menu.svg.import
├── update.svg
└── update.svg.import
├── compiler
├── compilation.gd
├── compilation.gd.uid
├── compiled_line.gd
├── compiled_line.gd.uid
├── compiler.gd
├── compiler.gd.uid
├── compiler_regex.gd
├── compiler_regex.gd.uid
├── compiler_result.gd
├── compiler_result.gd.uid
├── expression_parser.gd
├── expression_parser.gd.uid
├── resolved_goto_data.gd
├── resolved_goto_data.gd.uid
├── resolved_line_data.gd
├── resolved_line_data.gd.uid
├── resolved_tag_data.gd
├── resolved_tag_data.gd.uid
├── tree_line.gd
└── tree_line.gd.uid
├── components
├── code_edit.gd
├── code_edit.gd.uid
├── code_edit.tscn
├── code_edit_syntax_highlighter.gd
├── code_edit_syntax_highlighter.gd.uid
├── download_update_panel.gd
├── download_update_panel.gd.uid
├── download_update_panel.tscn
├── editor_property
│ ├── editor_property.gd
│ ├── editor_property.gd.uid
│ ├── editor_property_control.gd
│ ├── editor_property_control.gd.uid
│ ├── editor_property_control.tscn
│ ├── resource_button.gd
│ ├── resource_button.gd.uid
│ └── resource_button.tscn
├── errors_panel.gd
├── errors_panel.gd.uid
├── errors_panel.tscn
├── files_list.gd
├── files_list.gd.uid
├── files_list.tscn
├── find_in_files.gd
├── find_in_files.gd.uid
├── find_in_files.tscn
├── search_and_replace.gd
├── search_and_replace.gd.uid
├── search_and_replace.tscn
├── title_list.gd
├── title_list.gd.uid
├── title_list.tscn
├── update_button.gd
├── update_button.gd.uid
└── update_button.tscn
├── constants.gd
├── constants.gd.uid
├── dialogue_label.gd
├── dialogue_label.gd.uid
├── dialogue_label.tscn
├── dialogue_line.gd
├── dialogue_line.gd.uid
├── dialogue_manager.gd
├── dialogue_manager.gd.uid
├── dialogue_resource.gd
├── dialogue_resource.gd.uid
├── dialogue_response.gd
├── dialogue_response.gd.uid
├── dialogue_responses_menu.gd
├── dialogue_responses_menu.gd.uid
├── editor_translation_parser_plugin.gd
├── editor_translation_parser_plugin.gd.uid
├── example_balloon
├── ExampleBalloon.cs
├── ExampleBalloon.cs.uid
├── example_balloon.gd
├── example_balloon.gd.uid
├── example_balloon.tscn
└── small_example_balloon.tscn
├── export_plugin.gd
├── export_plugin.gd.uid
├── import_plugin.gd
├── import_plugin.gd.uid
├── inspector_plugin.gd
├── inspector_plugin.gd.uid
├── l10n
├── en.po
├── es.po
├── translations.pot
├── uk.po
├── zh.po
└── zh_TW.po
├── plugin.cfg
├── plugin.cfg.uid
├── plugin.gd
├── plugin.gd.uid
├── settings.gd
├── settings.gd.uid
├── test_scene.gd
├── test_scene.gd.uid
├── test_scene.tscn
├── utilities
├── builtins.gd
├── builtins.gd.uid
├── dialogue_cache.gd
└── dialogue_cache.gd.uid
└── views
├── main_view.gd
├── main_view.gd.uid
└── main_view.tscn
/addons/dialogue_manager/DialogueManager.cs.uid:
--------------------------------------------------------------------------------
1 | uid://c4c5lsrwy3opj
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022-present Nathan Hoad and Dialogue Manager contributors.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/assets/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
53 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/assets/icon.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://d3lr2uas6ax8v"
6 | path="res://.godot/imported/icon.svg-17eb5d3e2a3cfbe59852220758c5b7bd.ctex"
7 | metadata={
8 | "has_editor_variant": true,
9 | "vram_texture": false
10 | }
11 |
12 | [deps]
13 |
14 | source_file="res://addons/dialogue_manager/assets/icon.svg"
15 | dest_files=["res://.godot/imported/icon.svg-17eb5d3e2a3cfbe59852220758c5b7bd.ctex"]
16 |
17 | [params]
18 |
19 | compress/mode=0
20 | compress/high_quality=false
21 | compress/lossy_quality=0.7
22 | compress/hdr_compression=1
23 | compress/normal_map=0
24 | compress/channel_pack=0
25 | mipmaps/generate=false
26 | mipmaps/limit=-1
27 | roughness/mode=0
28 | roughness/src_normal=""
29 | process/fix_alpha_border=true
30 | process/premult_alpha=false
31 | process/normal_map_invert_y=false
32 | process/hdr_as_srgb=false
33 | process/hdr_clamp_exposure=false
34 | process/size_limit=0
35 | detect_3d/compress_to=1
36 | svg/scale=1.0
37 | editor/scale_with_editor_scale=true
38 | editor/convert_colors_with_editor_theme=true
39 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/assets/responses_menu.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
53 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/assets/responses_menu.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://drjfciwitjm83"
6 | path="res://.godot/imported/responses_menu.svg-87cf63ca685d53616205049572f4eb8f.ctex"
7 | metadata={
8 | "has_editor_variant": true,
9 | "vram_texture": false
10 | }
11 |
12 | [deps]
13 |
14 | source_file="res://addons/dialogue_manager/assets/responses_menu.svg"
15 | dest_files=["res://.godot/imported/responses_menu.svg-87cf63ca685d53616205049572f4eb8f.ctex"]
16 |
17 | [params]
18 |
19 | compress/mode=0
20 | compress/high_quality=false
21 | compress/lossy_quality=0.7
22 | compress/hdr_compression=1
23 | compress/normal_map=0
24 | compress/channel_pack=0
25 | mipmaps/generate=false
26 | mipmaps/limit=-1
27 | roughness/mode=0
28 | roughness/src_normal=""
29 | process/fix_alpha_border=true
30 | process/premult_alpha=false
31 | process/normal_map_invert_y=false
32 | process/hdr_as_srgb=false
33 | process/hdr_clamp_exposure=false
34 | process/size_limit=0
35 | detect_3d/compress_to=1
36 | svg/scale=1.0
37 | editor/scale_with_editor_scale=true
38 | editor/convert_colors_with_editor_theme=true
39 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/assets/update.svg.import:
--------------------------------------------------------------------------------
1 | [remap]
2 |
3 | importer="texture"
4 | type="CompressedTexture2D"
5 | uid="uid://d3baj6rygkb3f"
6 | path="res://.godot/imported/update.svg-f1628866ed4eb2e13e3b81f75443687e.ctex"
7 | metadata={
8 | "vram_texture": false
9 | }
10 |
11 | [deps]
12 |
13 | source_file="res://addons/dialogue_manager/assets/update.svg"
14 | dest_files=["res://.godot/imported/update.svg-f1628866ed4eb2e13e3b81f75443687e.ctex"]
15 |
16 | [params]
17 |
18 | compress/mode=0
19 | compress/high_quality=false
20 | compress/lossy_quality=0.7
21 | compress/hdr_compression=1
22 | compress/normal_map=0
23 | compress/channel_pack=0
24 | mipmaps/generate=false
25 | mipmaps/limit=-1
26 | roughness/mode=0
27 | roughness/src_normal=""
28 | process/fix_alpha_border=true
29 | process/premult_alpha=false
30 | process/normal_map_invert_y=false
31 | process/hdr_as_srgb=false
32 | process/hdr_clamp_exposure=false
33 | process/size_limit=0
34 | detect_3d/compress_to=1
35 | svg/scale=1.0
36 | editor/scale_with_editor_scale=false
37 | editor/convert_colors_with_editor_theme=false
38 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/compiler/compilation.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dsgpnyqg6cprg
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/compiler/compiled_line.gd:
--------------------------------------------------------------------------------
1 | ## A compiled line of dialogue.
2 | class_name DMCompiledLine extends RefCounted
3 |
4 |
5 | ## The ID of the line
6 | var id: String
7 | ## The translation key (or static line ID).
8 | var translation_key: String = ""
9 | ## The type of line.
10 | var type: String = ""
11 | ## The character name.
12 | var character: String = ""
13 | ## Any interpolation expressions for the character name.
14 | var character_replacements: Array[Dictionary] = []
15 | ## The text of the line.
16 | var text: String = ""
17 | ## Any interpolation expressions for the text.
18 | var text_replacements: Array[Dictionary] = []
19 | ## Any response siblings associated with this line.
20 | var responses: PackedStringArray = []
21 | ## Any randomise or case siblings for this line.
22 | var siblings: Array[Dictionary] = []
23 | ## Any lines said simultaneously.
24 | var concurrent_lines: PackedStringArray = []
25 | ## Any tags on this line.
26 | var tags: PackedStringArray = []
27 | ## The condition or mutation expression for this line.
28 | var expression: Dictionary = {}
29 | ## The express as the raw text that was given.
30 | var expression_text: String = ""
31 | ## The next sequential line to go to after this line.
32 | var next_id: String = ""
33 | ## The next line to go to after this line if it is unknown and compile time.
34 | var next_id_expression: Array[Dictionary] = []
35 | ## Whether this jump line should return after the jump target sequence has ended.
36 | var is_snippet: bool = false
37 | ## The ID of the next sibling line.
38 | var next_sibling_id: String = ""
39 | ## The ID after this line if it belongs to a block (eg. conditions).
40 | var next_id_after: String = ""
41 | ## Any doc comments attached to this line.
42 | var notes: String = ""
43 |
44 |
45 | #region Hooks
46 |
47 |
48 | func _init(initial_id: String, initial_type: String) -> void:
49 | id = initial_id
50 | type = initial_type
51 |
52 |
53 | func _to_string() -> String:
54 | var s: Array = [
55 | "[%s]" % [type],
56 | "%s:" % [character] if character != "" else null,
57 | text if text != "" else null,
58 | expression if expression.size() > 0 else null,
59 | "[%s]" % [",".join(tags)] if tags.size() > 0 else null,
60 | str(siblings) if siblings.size() > 0 else null,
61 | str(responses) if responses.size() > 0 else null,
62 | "=> END" if "end" in next_id else "=> %s" % [next_id],
63 | "(~> %s)" % [next_sibling_id] if next_sibling_id != "" else null,
64 | "(==> %s)" % [next_id_after] if next_id_after != "" else null,
65 | ].filter(func(item): return item != null)
66 |
67 | return " ".join(s)
68 |
69 |
70 | #endregion
71 |
72 | #region Helpers
73 |
74 |
75 | ## Express this line as a [Dictionary] that can be stored in a resource.
76 | func to_data() -> Dictionary:
77 | var d: Dictionary = {
78 | id = id,
79 | type = type,
80 | next_id = next_id
81 | }
82 |
83 | if next_id_expression.size() > 0:
84 | d.next_id_expression = next_id_expression
85 |
86 | match type:
87 | DMConstants.TYPE_CONDITION:
88 | d.condition = expression
89 | if not next_sibling_id.is_empty():
90 | d.next_sibling_id = next_sibling_id
91 | d.next_id_after = next_id_after
92 |
93 | DMConstants.TYPE_WHILE:
94 | d.condition = expression
95 | d.next_id_after = next_id_after
96 |
97 | DMConstants.TYPE_MATCH:
98 | d.condition = expression
99 | d.next_id_after = next_id_after
100 | d.cases = siblings
101 |
102 | DMConstants.TYPE_MUTATION:
103 | d.mutation = expression
104 |
105 | DMConstants.TYPE_GOTO:
106 | d.is_snippet = is_snippet
107 | d.next_id_after = next_id_after
108 | if not siblings.is_empty():
109 | d.siblings = siblings
110 |
111 | DMConstants.TYPE_RANDOM:
112 | d.siblings = siblings
113 |
114 | DMConstants.TYPE_RESPONSE:
115 | d.text = text
116 |
117 | if not responses.is_empty():
118 | d.responses = responses
119 |
120 | if translation_key != text:
121 | d.translation_key = translation_key
122 | if not expression.is_empty():
123 | d.condition = expression
124 | if not character.is_empty():
125 | d.character = character
126 | if not character_replacements.is_empty():
127 | d.character_replacements = character_replacements
128 | if not text_replacements.is_empty():
129 | d.text_replacements = text_replacements
130 | if not tags.is_empty():
131 | d.tags = tags
132 | if not notes.is_empty():
133 | d.notes = notes
134 | if not expression_text.is_empty():
135 | d.condition_as_text = expression_text
136 |
137 | DMConstants.TYPE_DIALOGUE:
138 | d.text = text
139 |
140 | if translation_key != text:
141 | d.translation_key = translation_key
142 |
143 | if not character.is_empty():
144 | d.character = character
145 | if not character_replacements.is_empty():
146 | d.character_replacements = character_replacements
147 | if not text_replacements.is_empty():
148 | d.text_replacements = text_replacements
149 | if not tags.is_empty():
150 | d.tags = tags
151 | if not notes.is_empty():
152 | d.notes = notes
153 | if not siblings.is_empty():
154 | d.siblings = siblings
155 | if not concurrent_lines.is_empty():
156 | d.concurrent_lines = concurrent_lines
157 |
158 | return d
159 |
160 |
161 | #endregion
162 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/compiler/compiled_line.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dg8j5hudp4210
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/compiler/compiler.gd:
--------------------------------------------------------------------------------
1 | ## A compiler of Dialogue Manager dialogue.
2 | class_name DMCompiler extends RefCounted
3 |
4 |
5 | ## Compile a dialogue script.
6 | static func compile_string(text: String, path: String) -> DMCompilerResult:
7 | var compilation: DMCompilation = DMCompilation.new()
8 | compilation.compile(text, path)
9 |
10 | var result: DMCompilerResult = DMCompilerResult.new()
11 | result.imported_paths = compilation.imported_paths
12 | result.using_states = compilation.using_states
13 | result.character_names = compilation.character_names
14 | result.titles = compilation.titles
15 | result.first_title = compilation.first_title
16 | result.errors = compilation.errors
17 | result.lines = compilation.data
18 | result.raw_text = text
19 |
20 | return result
21 |
22 |
23 | ## Get the line type of a string. The returned string will match one of the [code]TYPE_[/code] constants of [DMConstants].
24 | static func get_line_type(text: String) -> String:
25 | var compilation: DMCompilation = DMCompilation.new()
26 | return compilation.get_line_type(text)
27 |
28 |
29 | ## Get the static line ID (eg. [code][ID:SOMETHING][/code]) of some text.
30 | static func get_static_line_id(text: String) -> String:
31 | var compilation: DMCompilation = DMCompilation.new()
32 | return compilation.extract_static_line_id(text)
33 |
34 |
35 | ## Get the translatable part of a line.
36 | static func extract_translatable_string(text: String) -> String:
37 | var compilation: DMCompilation = DMCompilation.new()
38 |
39 | var tree_line = DMTreeLine.new("")
40 | tree_line.text = text
41 | var line: DMCompiledLine = DMCompiledLine.new("", compilation.get_line_type(text))
42 | compilation.parse_character_and_dialogue(tree_line, line, [tree_line], 0, null)
43 |
44 | return line.text
45 |
46 |
47 | ## Get the known titles in a dialogue script.
48 | static func get_titles_in_text(text: String, path: String) -> Dictionary:
49 | var compilation: DMCompilation = DMCompilation.new()
50 | compilation.build_line_tree(compilation.inject_imported_files(text, path))
51 | return compilation.titles
52 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/compiler/compiler.gd.uid:
--------------------------------------------------------------------------------
1 | uid://chtfdmr0cqtp4
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/compiler/compiler_regex.gd:
--------------------------------------------------------------------------------
1 | ## A collection of [RegEx] for use by the [DMCompiler].
2 | class_name DMCompilerRegEx extends RefCounted
3 |
4 |
5 | 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}]+)")
6 | var USING_REGEX: RegEx = RegEx.create_from_string("^using (?.*)$")
7 | var INDENT_REGEX: RegEx = RegEx.create_from_string("^\\t+")
8 | 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}]*$")
9 | var BEGINS_WITH_NUMBER_REGEX: RegEx = RegEx.create_from_string("^\\d")
10 | var CONDITION_REGEX: RegEx = RegEx.create_from_string("(if|elif|while|else if|match|when) (?.*)\\:?")
11 | var WRAPPED_CONDITION_REGEX: RegEx = RegEx.create_from_string("\\[if (?.*)\\]")
12 | var MUTATION_REGEX: RegEx = RegEx.create_from_string("(?do|do!|set) (?.*)")
13 | var STATIC_LINE_ID_REGEX: RegEx = RegEx.create_from_string("\\[ID:(?.*?)\\]")
14 | var WEIGHTED_RANDOM_SIBLINGS_REGEX: RegEx = RegEx.create_from_string("^\\%(?[\\d.]+)?( \\[if (?.+?)\\])? ")
15 | var GOTO_REGEX: RegEx = RegEx.create_from_string("=> (?.*)")
16 |
17 | var INLINE_RANDOM_REGEX: RegEx = RegEx.create_from_string("\\[\\[(?.*?)\\]\\]")
18 | var INLINE_CONDITIONALS_REGEX: RegEx = RegEx.create_from_string("\\[if (?.+?)\\](?.*?)\\[\\/if\\]")
19 |
20 | var TAGS_REGEX: RegEx = RegEx.create_from_string("\\[#(?.*?)\\]")
21 |
22 | var REPLACEMENTS_REGEX: RegEx = RegEx.create_from_string("{{(.*?)}}")
23 |
24 | var ALPHA_NUMERIC: RegEx = RegEx.create_from_string("[^a-zA-Z0-9\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]+")
25 |
26 | var TOKEN_DEFINITIONS: Dictionary = {
27 | 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}]*\\("),
28 | 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}]*\\["),
29 | DMConstants.TOKEN_PARENS_OPEN: RegEx.create_from_string("^\\("),
30 | DMConstants.TOKEN_PARENS_CLOSE: RegEx.create_from_string("^\\)"),
31 | DMConstants.TOKEN_BRACKET_OPEN: RegEx.create_from_string("^\\["),
32 | DMConstants.TOKEN_BRACKET_CLOSE: RegEx.create_from_string("^\\]"),
33 | DMConstants.TOKEN_BRACE_OPEN: RegEx.create_from_string("^\\{"),
34 | DMConstants.TOKEN_BRACE_CLOSE: RegEx.create_from_string("^\\}"),
35 | DMConstants.TOKEN_COLON: RegEx.create_from_string("^:"),
36 | DMConstants.TOKEN_COMPARISON: RegEx.create_from_string("^(==|<=|>=|<|>|!=|in )"),
37 | DMConstants.TOKEN_ASSIGNMENT: RegEx.create_from_string("^(\\+=|\\-=|\\*=|/=|=)"),
38 | DMConstants.TOKEN_NUMBER: RegEx.create_from_string("^\\-?\\d+(\\.\\d+)?"),
39 | DMConstants.TOKEN_OPERATOR: RegEx.create_from_string("^(\\+|\\-|\\*|/|%)"),
40 | DMConstants.TOKEN_COMMA: RegEx.create_from_string("^,"),
41 | DMConstants.TOKEN_NULL_COALESCE: RegEx.create_from_string("^\\?\\."),
42 | DMConstants.TOKEN_DOT: RegEx.create_from_string("^\\."),
43 | DMConstants.TOKEN_STRING: RegEx.create_from_string("^&?(\".*?\"|\'.*?\')"),
44 | DMConstants.TOKEN_NOT: RegEx.create_from_string("^(not( |$)|!)"),
45 | DMConstants.TOKEN_AND_OR: RegEx.create_from_string("^(and|or|&&|\\|\\|)( |$)"),
46 | 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}]*"),
47 | DMConstants.TOKEN_COMMENT: RegEx.create_from_string("^#.*"),
48 | DMConstants.TOKEN_CONDITION: RegEx.create_from_string("^(if|elif|else)"),
49 | DMConstants.TOKEN_BOOL: RegEx.create_from_string("^(true|false)")
50 | }
51 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/compiler/compiler_regex.gd.uid:
--------------------------------------------------------------------------------
1 | uid://d3tvcrnicjibp
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/compiler/compiler_result.gd:
--------------------------------------------------------------------------------
1 | ## The result of using the [DMCompiler] to compile some dialogue.
2 | class_name DMCompilerResult extends RefCounted
3 |
4 |
5 | ## Any paths that were imported into the compiled dialogue file.
6 | var imported_paths: PackedStringArray = []
7 |
8 | ## Any "using" directives.
9 | var using_states: PackedStringArray = []
10 |
11 | ## All titles in the file and the line they point to.
12 | var titles: Dictionary = {}
13 |
14 | ## The first title in the file.
15 | var first_title: String = ""
16 |
17 | ## All character names.
18 | var character_names: PackedStringArray = []
19 |
20 | ## Any compilation errors.
21 | var errors: Array[Dictionary] = []
22 |
23 | ## A map of all compiled lines.
24 | var lines: Dictionary = {}
25 |
26 | ## The raw dialogue text.
27 | var raw_text: String = ""
28 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/compiler/compiler_result.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dmk74tknimqvg
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/compiler/expression_parser.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dbi4hbar8ubwu
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/compiler/resolved_goto_data.gd:
--------------------------------------------------------------------------------
1 | ## Data associated with a dialogue jump/goto line.
2 | class_name DMResolvedGotoData extends RefCounted
3 |
4 |
5 | ## The title that was specified
6 | var title: String = ""
7 | ## The target line's ID
8 | var next_id: String = ""
9 | ## An expression to determine the target line at runtime.
10 | var expression: Array[Dictionary] = []
11 | ## The given line text with the jump syntax removed.
12 | var text_without_goto: String = ""
13 | ## Whether this is a jump-and-return style jump.
14 | var is_snippet: bool = false
15 | ## A parse error if there was one.
16 | var error: int
17 | ## The index in the string where
18 | var index: int = 0
19 |
20 | # An instance of the compiler [RegEx] list.
21 | var regex: DMCompilerRegEx = DMCompilerRegEx.new()
22 |
23 |
24 | func _init(text: String, titles: Dictionary) -> void:
25 | if not "=> " in text and not "=>< " in text: return
26 |
27 | if "=> " in text:
28 | text_without_goto = text.substr(0, text.find("=> ")).strip_edges()
29 | elif "=>< " in text:
30 | is_snippet = true
31 | text_without_goto = text.substr(0, text.find("=>< ")).strip_edges()
32 |
33 | var found: RegExMatch = regex.GOTO_REGEX.search(text)
34 | if found == null:
35 | return
36 |
37 | title = found.strings[found.names.goto].strip_edges()
38 | index = found.get_start(0)
39 |
40 | if title == "":
41 | error = DMConstants.ERR_UNKNOWN_TITLE
42 | return
43 |
44 | # "=> END!" means end the conversation, ignoring any "=><" chains.
45 | if title == "END!":
46 | next_id = DMConstants.ID_END_CONVERSATION
47 |
48 | # "=> END" means end the current title (and go back to the previous one if there is one
49 | # in the stack)
50 | elif title == "END":
51 | next_id = DMConstants.ID_END
52 |
53 | elif titles.has(title):
54 | next_id = titles.get(title)
55 | elif title.begins_with("{{"):
56 | var expression_parser: DMExpressionParser = DMExpressionParser.new()
57 | var title_expression: Array[Dictionary] = expression_parser.extract_replacements(title, 0)
58 | if title_expression[0].has("error"):
59 | error = title_expression[0].error
60 | else:
61 | expression = title_expression[0].expression
62 | else:
63 | next_id = title
64 | error = DMConstants.ERR_UNKNOWN_TITLE
65 |
66 |
67 | func _to_string() -> String:
68 | return "%s =>%s %s (%s)" % [text_without_goto, "<" if is_snippet else "", title, next_id]
69 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/compiler/resolved_goto_data.gd.uid:
--------------------------------------------------------------------------------
1 | uid://llhl5pt47eoq
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/compiler/resolved_line_data.gd:
--------------------------------------------------------------------------------
1 | ## Any data associated with inline dialogue BBCodes.
2 | class_name DMResolvedLineData extends RefCounted
3 |
4 | ## The line's text
5 | var text: String = ""
6 | ## A map of pauses against where they are found in the text.
7 | var pauses: Dictionary = {}
8 | ## A map of speed changes against where they are found in the text.
9 | var speeds: Dictionary = {}
10 | ## A list of any mutations to run and where they are found in the text.
11 | var mutations: Array[Array] = []
12 | ## A duration reference for the line. Represented as "auto" or a stringified number.
13 | var time: String = ""
14 |
15 |
16 | func _init(line: String) -> void:
17 | text = line
18 | pauses = {}
19 | speeds = {}
20 | mutations = []
21 | time = ""
22 |
23 | var bbcodes: Array = []
24 |
25 | # Remove any escaped brackets (ie. "\[")
26 | var escaped_open_brackets: PackedInt32Array = []
27 | var escaped_close_brackets: PackedInt32Array = []
28 | for i in range(0, text.length() - 1):
29 | if text.substr(i, 2) == "\\[":
30 | text = text.substr(0, i) + "!" + text.substr(i + 2)
31 | escaped_open_brackets.append(i)
32 | elif text.substr(i, 2) == "\\]":
33 | text = text.substr(0, i) + "!" + text.substr(i + 2)
34 | escaped_close_brackets.append(i)
35 |
36 | # Extract all of the BB codes so that we know the actual text (we could do this easier with
37 | # a RichTextLabel but then we'd need to await idle_frame which is annoying)
38 | var bbcode_positions = find_bbcode_positions_in_string(text)
39 | var accumulaive_length_offset = 0
40 | for position in bbcode_positions:
41 | # Ignore our own markers
42 | if position.code in ["wait", "speed", "/speed", "do", "do!", "set", "next", "if", "else", "/if"]:
43 | continue
44 |
45 | bbcodes.append({
46 | bbcode = position.bbcode,
47 | start = position.start,
48 | offset_start = position.start - accumulaive_length_offset
49 | })
50 | accumulaive_length_offset += position.bbcode.length()
51 |
52 | for bb in bbcodes:
53 | text = text.substr(0, bb.offset_start) + text.substr(bb.offset_start + bb.bbcode.length())
54 |
55 | # Now find any dialogue markers
56 | var next_bbcode_position = find_bbcode_positions_in_string(text, false)
57 | var limit = 0
58 | while next_bbcode_position.size() > 0 and limit < 1000:
59 | limit += 1
60 |
61 | var bbcode = next_bbcode_position[0]
62 |
63 | var index = bbcode.start
64 | var code = bbcode.code
65 | var raw_args = bbcode.raw_args
66 | var args = {}
67 | if code in ["do", "do!", "set"]:
68 | var compilation: DMCompilation = DMCompilation.new()
69 | args["value"] = compilation.extract_mutation("%s %s" % [code, raw_args])
70 | else:
71 | # Could be something like:
72 | # "=1.0"
73 | # " rate=20 level=10"
74 | if raw_args and raw_args[0] == "=":
75 | raw_args = "value" + raw_args
76 | for pair in raw_args.strip_edges().split(" "):
77 | if "=" in pair:
78 | var bits = pair.split("=")
79 | args[bits[0]] = bits[1]
80 |
81 | match code:
82 | "wait":
83 | if pauses.has(index):
84 | pauses[index] += args.get("value").to_float()
85 | else:
86 | pauses[index] = args.get("value").to_float()
87 | "speed":
88 | speeds[index] = args.get("value").to_float()
89 | "/speed":
90 | speeds[index] = 1.0
91 | "do", "do!", "set":
92 | mutations.append([index, args.get("value")])
93 | "next":
94 | time = args.get("value") if args.has("value") else "0"
95 |
96 | # Find any BB codes that are after this index and remove the length from their start
97 | var length = bbcode.bbcode.length()
98 | for bb in bbcodes:
99 | if bb.offset_start > bbcode.start:
100 | bb.offset_start -= length
101 | bb.start -= length
102 |
103 | # Find any escaped brackets after this that need moving
104 | for i in range(0, escaped_open_brackets.size()):
105 | if escaped_open_brackets[i] > bbcode.start:
106 | escaped_open_brackets[i] -= length
107 | for i in range(0, escaped_close_brackets.size()):
108 | if escaped_close_brackets[i] > bbcode.start:
109 | escaped_close_brackets[i] -= length
110 |
111 | text = text.substr(0, index) + text.substr(index + length)
112 | next_bbcode_position = find_bbcode_positions_in_string(text, false)
113 |
114 | # Put the BB Codes back in
115 | for bb in bbcodes:
116 | text = text.insert(bb.start, bb.bbcode)
117 |
118 | # Put the escaped brackets back in
119 | for index in escaped_open_brackets:
120 | text = text.left(index) + "[" + text.right(text.length() - index - 1)
121 | for index in escaped_close_brackets:
122 | text = text.left(index) + "]" + text.right(text.length() - index - 1)
123 |
124 |
125 | func find_bbcode_positions_in_string(string: String, find_all: bool = true, include_conditions: bool = false) -> Array[Dictionary]:
126 | if not "[" in string: return []
127 |
128 | var positions: Array[Dictionary] = []
129 |
130 | var open_brace_count: int = 0
131 | var start: int = 0
132 | var bbcode: String = ""
133 | var code: String = ""
134 | var is_finished_code: bool = false
135 | for i in range(0, string.length()):
136 | if string[i] == "[":
137 | if open_brace_count == 0:
138 | start = i
139 | bbcode = ""
140 | code = ""
141 | is_finished_code = false
142 | open_brace_count += 1
143 |
144 | else:
145 | if not is_finished_code and (string[i].to_upper() != string[i] or string[i] == "/" or string[i] == "!"):
146 | code += string[i]
147 | else:
148 | is_finished_code = true
149 |
150 | if open_brace_count > 0:
151 | bbcode += string[i]
152 |
153 | if string[i] == "]":
154 | open_brace_count -= 1
155 | if open_brace_count == 0 and (include_conditions or not code in ["if", "else", "/if"]):
156 | positions.append({
157 | bbcode = bbcode,
158 | code = code,
159 | start = start,
160 | end = i,
161 | raw_args = bbcode.substr(code.length() + 1, bbcode.length() - code.length() - 2).strip_edges()
162 | })
163 |
164 | if not find_all:
165 | return positions
166 |
167 | return positions
168 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/compiler/resolved_line_data.gd.uid:
--------------------------------------------------------------------------------
1 | uid://0k6q8kukq0qa
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/compiler/resolved_tag_data.gd:
--------------------------------------------------------------------------------
1 | ## Tag data associated with a line of dialogue.
2 | class_name DMResolvedTagData extends RefCounted
3 |
4 |
5 | ## The list of tags.
6 | var tags: PackedStringArray = []
7 | ## The line with any tag syntax removed.
8 | var text_without_tags: String = ""
9 |
10 | # An instance of the compiler [RegEx].
11 | var regex: DMCompilerRegEx = DMCompilerRegEx.new()
12 |
13 |
14 | func _init(text: String) -> void:
15 | var resolved_tags: PackedStringArray = []
16 | var tag_matches: Array[RegExMatch] = regex.TAGS_REGEX.search_all(text)
17 | for tag_match in tag_matches:
18 | text = text.replace(tag_match.get_string(), "")
19 | var tags = tag_match.get_string().replace("[#", "").replace("]", "").replace(", ", ",").split(",")
20 | for tag in tags:
21 | tag = tag.replace("#", "")
22 | if not tag in resolved_tags:
23 | resolved_tags.append(tag)
24 |
25 | tags = resolved_tags
26 | text_without_tags = text
27 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/compiler/resolved_tag_data.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cqai3ikuilqfq
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/compiler/tree_line.gd:
--------------------------------------------------------------------------------
1 | ## An intermediate representation of a dialogue line before it gets compiled.
2 | class_name DMTreeLine extends RefCounted
3 |
4 |
5 | ## The line number where this dialogue was found (after imported files have had their content imported).
6 | var line_number: int = 0
7 | ## The parent [DMTreeLine] of this line.
8 | ## This is stored as a Weak Reference so that this RefCounted can elegantly free itself.
9 | ## Without it being a Weak Reference, this can easily cause a cyclical reference that keeps this resource alive.
10 | var parent: WeakRef
11 | ## The ID of this line.
12 | var id: String
13 | ## The type of this line (as a [String] defined in [DMConstants].
14 | var type: String = ""
15 | ## Is this line part of a randomised group?
16 | var is_random: bool = false
17 | ## The indent count for this line.
18 | var indent: int = 0
19 | ## The text of this line.
20 | var text: String = ""
21 | ## The child [DMTreeLine]s of this line.
22 | var children: Array[DMTreeLine] = []
23 | ## Any doc comments attached to this line.
24 | var notes: String = ""
25 | ## Is this a dialogue line that is the child of another dialogue line?
26 | var is_nested_dialogue: bool = false
27 |
28 |
29 | func _init(initial_id: String) -> void:
30 | id = initial_id
31 |
32 |
33 | func _to_string() -> String:
34 | var tabs = []
35 | tabs.resize(indent)
36 | tabs.fill("\t")
37 | tabs = "".join(tabs)
38 |
39 | return tabs.join([tabs + "{\n",
40 | "\tid: %s\n" % [id],
41 | "\ttype: %s\n" % [type],
42 | "\tis_random: %s\n" % ["true" if is_random else "false"],
43 | "\ttext: %s\n" % [text],
44 | "\tnotes: %s\n" % [notes],
45 | "\tchildren: []\n" if children.size() == 0 else "\tchildren: [\n" + ",\n".join(children.map(func(child): return str(child))) + "]\n",
46 | "}"])
47 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/compiler/tree_line.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dsu4i84dpif14
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/code_edit.gd.uid:
--------------------------------------------------------------------------------
1 | uid://djeybvlb332mp
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/code_edit.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=4 format=3 uid="uid://civ6shmka5e8u"]
2 |
3 | [ext_resource type="Script" uid="uid://klpiq4tk3t7a" path="res://addons/dialogue_manager/components/code_edit_syntax_highlighter.gd" id="1_58cfo"]
4 | [ext_resource type="Script" uid="uid://djeybvlb332mp" path="res://addons/dialogue_manager/components/code_edit.gd" id="1_g324i"]
5 |
6 | [sub_resource type="SyntaxHighlighter" id="SyntaxHighlighter_cobxx"]
7 | script = ExtResource("1_58cfo")
8 |
9 | [node name="CodeEdit" type="CodeEdit"]
10 | anchors_preset = 15
11 | anchor_right = 1.0
12 | anchor_bottom = 1.0
13 | grow_horizontal = 2
14 | grow_vertical = 2
15 | text = "~ title_thing
16 |
17 | if this = \"that\" or 'this'
18 | Nathan: Something
19 | - Then [if test.thing() == 2.0] => somewhere
20 | - Other => END!
21 |
22 | ~ somewhere
23 |
24 | set has_something = true
25 | => END"
26 | highlight_all_occurrences = true
27 | highlight_current_line = true
28 | draw_tabs = true
29 | syntax_highlighter = SubResource("SyntaxHighlighter_cobxx")
30 | scroll_past_end_of_file = true
31 | minimap_draw = true
32 | symbol_lookup_on_click = true
33 | line_folding = true
34 | gutters_draw_line_numbers = true
35 | gutters_draw_fold_gutter = true
36 | delimiter_strings = Array[String](["\" \""])
37 | delimiter_comments = Array[String](["#"])
38 | code_completion_enabled = true
39 | code_completion_prefixes = Array[String]([">", "<"])
40 | indent_automatic = true
41 | auto_brace_completion_enabled = true
42 | auto_brace_completion_highlight_matching = true
43 | auto_brace_completion_pairs = {
44 | "\"": "\"",
45 | "(": ")",
46 | "[": "]",
47 | "{": "}"
48 | }
49 | script = ExtResource("1_g324i")
50 |
51 | [connection signal="caret_changed" from="." to="." method="_on_code_edit_caret_changed"]
52 | [connection signal="gutter_clicked" from="." to="." method="_on_code_edit_gutter_clicked"]
53 | [connection signal="symbol_lookup" from="." to="." method="_on_code_edit_symbol_lookup"]
54 | [connection signal="symbol_validate" from="." to="." method="_on_code_edit_symbol_validate"]
55 | [connection signal="text_changed" from="." to="." method="_on_code_edit_text_changed"]
56 | [connection signal="text_set" from="." to="." method="_on_code_edit_text_set"]
57 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/code_edit_syntax_highlighter.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | class_name DMSyntaxHighlighter extends SyntaxHighlighter
3 |
4 |
5 | var regex: DMCompilerRegEx = DMCompilerRegEx.new()
6 | var compilation: DMCompilation = DMCompilation.new()
7 | var expression_parser = DMExpressionParser.new()
8 |
9 | var cache: Dictionary = {}
10 |
11 |
12 | func _clear_highlighting_cache() -> void:
13 | cache.clear()
14 |
15 |
16 | func _get_line_syntax_highlighting(line: int) -> Dictionary:
17 | expression_parser.include_comments = true
18 |
19 | var colors: Dictionary = {}
20 | var text_edit: TextEdit = get_text_edit()
21 | var text: String = text_edit.get_line(line)
22 |
23 | # Prevent an error from popping up while developing
24 | if not is_instance_valid(text_edit) or text_edit.theme_overrides.is_empty():
25 | return colors
26 |
27 | # Disable this, as well as the line at the bottom of this function to remove the cache.
28 | if text in cache:
29 | return cache[text]
30 |
31 | var theme: Dictionary = text_edit.theme_overrides
32 |
33 | var index: int = 0
34 |
35 | match DMCompiler.get_line_type(text):
36 | DMConstants.TYPE_USING:
37 | colors[index] = { color = theme.conditions_color }
38 | colors[index + "using ".length()] = { color = theme.text_color }
39 |
40 | DMConstants.TYPE_IMPORT:
41 | colors[index] = { color = theme.conditions_color }
42 | var import: RegExMatch = regex.IMPORT_REGEX.search(text)
43 | if import:
44 | colors[index + import.get_start("path") - 1] = { color = theme.strings_color }
45 | colors[index + import.get_end("path") + 1] = { color = theme.conditions_color }
46 | colors[index + import.get_start("prefix")] = { color = theme.text_color }
47 |
48 | DMConstants.TYPE_COMMENT:
49 | colors[index] = { color = theme.comments_color }
50 |
51 | DMConstants.TYPE_TITLE:
52 | colors[index] = { color = theme.titles_color }
53 |
54 | DMConstants.TYPE_CONDITION, DMConstants.TYPE_WHILE, DMConstants.TYPE_MATCH, DMConstants.TYPE_WHEN:
55 | colors[0] = { color = theme.conditions_color }
56 | index = text.find(" ")
57 | if index > -1:
58 | var expression: Array = expression_parser.tokenise(text.substr(index), DMConstants.TYPE_CONDITION, 0)
59 | if expression.size() == 0:
60 | colors[index] = { color = theme.critical_color }
61 | else:
62 | _highlight_expression(expression, colors, index)
63 |
64 | DMConstants.TYPE_MUTATION:
65 | colors[0] = { color = theme.mutations_color }
66 | index = text.find(" ")
67 | var expression: Array = expression_parser.tokenise(text.substr(index), DMConstants.TYPE_MUTATION, 0)
68 | if expression.size() == 0:
69 | colors[index] = { color = theme.critical_color }
70 | else:
71 | _highlight_expression(expression, colors, index)
72 |
73 | DMConstants.TYPE_GOTO:
74 | if text.strip_edges().begins_with("%"):
75 | colors[index] = { color = theme.symbols_color }
76 | index = text.find(" ")
77 | _highlight_goto(text, colors, index)
78 |
79 | DMConstants.TYPE_RANDOM:
80 | colors[index] = { color = theme.symbols_color }
81 |
82 | DMConstants.TYPE_DIALOGUE, DMConstants.TYPE_RESPONSE:
83 | if text.strip_edges().begins_with("%"):
84 | colors[index] = { color = theme.symbols_color }
85 | index = text.find(" ", text.find("%"))
86 | colors[index] = { color = theme.text_color.lerp(theme.symbols_color, 0.5) }
87 |
88 | var dialogue_text: String = text.substr(index, text.find("=>"))
89 |
90 | # Highlight character name (but ignore ":" within line ID reference)
91 | var split_index: int = dialogue_text.replace("\\:", "??").find(":")
92 | if text.substr(split_index - 3, 3) != "[ID":
93 | colors[index + split_index + 1] = { color = theme.text_color }
94 | else:
95 | # If there's no character name then just highlight the text as dialogue.
96 | colors[index] = { color = theme.text_color }
97 |
98 | # Interpolation
99 | var replacements: Array[RegExMatch] = regex.REPLACEMENTS_REGEX.search_all(dialogue_text)
100 | for replacement: RegExMatch in replacements:
101 | var expression_text: String = replacement.get_string().substr(0, replacement.get_string().length() - 2).substr(2)
102 | var expression: Array = expression_parser.tokenise(expression_text, DMConstants.TYPE_MUTATION, replacement.get_start())
103 | var expression_index: int = index + replacement.get_start()
104 | colors[expression_index] = { color = theme.symbols_color }
105 | if expression.size() == 0 or expression[0].type == DMConstants.TYPE_ERROR:
106 | colors[expression_index] = { color = theme.critical_color }
107 | else:
108 | _highlight_expression(expression, colors, index + 2)
109 | colors[expression_index + expression_text.length() + 2] = { color = theme.symbols_color }
110 | colors[expression_index + expression_text.length() + 4] = { color = theme.text_color }
111 | # Tags (and inline mutations)
112 | var resolved_line_data: DMResolvedLineData = DMResolvedLineData.new("")
113 | var bbcodes: Array[Dictionary] = resolved_line_data.find_bbcode_positions_in_string(dialogue_text, true, true)
114 | for bbcode: Dictionary in bbcodes:
115 | var tag: String = bbcode.code
116 | var code: String = bbcode.raw_args
117 | if code.begins_with("["):
118 | colors[index + bbcode.start] = { color = theme.symbols_color }
119 | colors[index + bbcode.start + 2] = { color = theme.text_color }
120 | var pipe_cursor: int = code.find("|")
121 | while pipe_cursor > -1:
122 | colors[index + bbcode.start + pipe_cursor + 1] = { color = theme.symbols_color }
123 | colors[index + bbcode.start + pipe_cursor + 2] = { color = theme.text_color }
124 | pipe_cursor = code.find("|", pipe_cursor + 1)
125 | colors[index + bbcode.end - 1] = { color = theme.symbols_color }
126 | colors[index + bbcode.end + 1] = { color = theme.text_color }
127 | else:
128 | colors[index + bbcode.start] = { color = theme.symbols_color }
129 | if tag.begins_with("do") or tag.begins_with("set") or tag.begins_with("if"):
130 | if tag.begins_with("if"):
131 | colors[index + bbcode.start + 1] = { color = theme.conditions_color }
132 | else:
133 | colors[index + bbcode.start + 1] = { color = theme.mutations_color }
134 | var expression: Array = expression_parser.tokenise(code, DMConstants.TYPE_MUTATION, bbcode.start + bbcode.code.length())
135 | if expression.size() == 0 or expression[0].type == DMConstants.TYPE_ERROR:
136 | colors[index + bbcode.start + tag.length() + 1] = { color = theme.critical_color }
137 | else:
138 | _highlight_expression(expression, colors, index + 2)
139 | # else and closing if have no expression
140 | elif tag.begins_with("else") or tag.begins_with("/if"):
141 | colors[index + bbcode.start + 1] = { color = theme.conditions_color }
142 | colors[index + bbcode.end] = { color = theme.symbols_color }
143 | colors[index + bbcode.end + 1] = { color = theme.text_color }
144 | # Jumps
145 | if "=> " in text or "=>< " in text:
146 | _highlight_goto(text, colors, index)
147 |
148 | # Order the dictionary keys to prevent CodeEdit from having issues
149 | var ordered_colors: Dictionary = {}
150 | var ordered_keys: Array = colors.keys()
151 | ordered_keys.sort()
152 | for key_index: int in ordered_keys:
153 | ordered_colors[key_index] = colors[key_index]
154 |
155 | cache[text] = ordered_colors
156 | return ordered_colors
157 |
158 |
159 | func _highlight_expression(tokens: Array, colors: Dictionary, index: int) -> int:
160 | var theme: Dictionary = get_text_edit().theme_overrides
161 | var last_index: int = index
162 | for token: Dictionary in tokens:
163 | last_index = token.i
164 | match token.type:
165 | DMConstants.TOKEN_COMMENT:
166 | colors[index + token.i] = { color = theme.comments_color }
167 |
168 | DMConstants.TOKEN_CONDITION, DMConstants.TOKEN_AND_OR:
169 | colors[index + token.i] = { color = theme.conditions_color }
170 |
171 | DMConstants.TOKEN_VARIABLE:
172 | if token.value in ["true", "false"]:
173 | colors[index + token.i] = { color = theme.conditions_color }
174 | else:
175 | colors[index + token.i] = { color = theme.members_color }
176 |
177 | DMConstants.TOKEN_OPERATOR, DMConstants.TOKEN_COLON, \
178 | DMConstants.TOKEN_COMMA, DMConstants.TOKEN_DOT, DMConstants.TOKEN_NULL_COALESCE, \
179 | DMConstants.TOKEN_NUMBER, DMConstants.TOKEN_ASSIGNMENT:
180 | colors[index + token.i] = { color = theme.symbols_color }
181 |
182 | DMConstants.TOKEN_STRING:
183 | colors[index + token.i] = { color = theme.strings_color }
184 |
185 | DMConstants.TOKEN_FUNCTION:
186 | colors[index + token.i] = { color = theme.mutations_color }
187 | colors[index + token.i + token.function.length()] = { color = theme.symbols_color }
188 | for parameter: Array in token.value:
189 | last_index = _highlight_expression(parameter, colors, index)
190 | DMConstants.TOKEN_PARENS_CLOSE:
191 | colors[index + token.i] = { color = theme.symbols_color }
192 |
193 | DMConstants.TOKEN_DICTIONARY_REFERENCE:
194 | colors[index + token.i] = { color = theme.members_color }
195 | colors[index + token.i + token.variable.length()] = { color = theme.symbols_color }
196 | last_index = _highlight_expression(token.value, colors, index)
197 | DMConstants.TOKEN_ARRAY:
198 | colors[index + token.i] = { color = theme.symbols_color }
199 | for item: Array in token.value:
200 | last_index = _highlight_expression(item, colors, index)
201 | DMConstants.TOKEN_BRACKET_CLOSE:
202 | colors[index + token.i] = { color = theme.symbols_color }
203 |
204 | DMConstants.TOKEN_DICTIONARY:
205 | colors[index + token.i] = { color = theme.symbols_color }
206 | last_index = _highlight_expression(token.value.keys() + token.value.values(), colors, index)
207 | DMConstants.TOKEN_BRACE_CLOSE:
208 | colors[index + token.i] = { color = theme.symbols_color }
209 | last_index += 1
210 |
211 | DMConstants.TOKEN_GROUP:
212 | last_index = _highlight_expression(token.value, colors, index)
213 |
214 | return last_index
215 |
216 |
217 | func _highlight_goto(text: String, colors: Dictionary, index: int) -> int:
218 | var theme: Dictionary = get_text_edit().theme_overrides
219 | var goto_data: DMResolvedGotoData = DMResolvedGotoData.new(text, {})
220 | colors[goto_data.index] = { color = theme.jumps_color }
221 | if "{{" in text:
222 | index = text.find("{{", goto_data.index)
223 | var last_index: int = 0
224 | if goto_data.error:
225 | colors[index + 2] = { color = theme.critical_color }
226 | else:
227 | last_index = _highlight_expression(goto_data.expression, colors, index)
228 | index = text.find("}}", index + last_index)
229 | colors[index] = { color = theme.jumps_color }
230 |
231 | return index
232 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/code_edit_syntax_highlighter.gd.uid:
--------------------------------------------------------------------------------
1 | uid://klpiq4tk3t7a
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/download_update_panel.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | extends Control
3 |
4 |
5 | signal failed()
6 | signal updated(updated_to_version: String)
7 |
8 |
9 | const DialogueConstants = preload("../constants.gd")
10 |
11 | const TEMP_FILE_NAME = "user://temp.zip"
12 |
13 |
14 | @onready var logo: TextureRect = %Logo
15 | @onready var label: Label = $VBox/Label
16 | @onready var http_request: HTTPRequest = $HTTPRequest
17 | @onready var download_button: Button = %DownloadButton
18 |
19 | var next_version_release: Dictionary:
20 | set(value):
21 | next_version_release = value
22 | label.text = DialogueConstants.translate(&"update.is_available_for_download") % value.tag_name.substr(1)
23 | get:
24 | return next_version_release
25 |
26 |
27 | func _ready() -> void:
28 | $VBox/Center/DownloadButton.text = DialogueConstants.translate(&"update.download_update")
29 | $VBox/Center2/NotesButton.text = DialogueConstants.translate(&"update.release_notes")
30 |
31 |
32 | ### Signals
33 |
34 |
35 | func _on_download_button_pressed() -> void:
36 | # Safeguard the actual dialogue manager repo from accidentally updating itself
37 | if FileAccess.file_exists("res://tests/test_basic_dialogue.gd"):
38 | prints("You can't update the addon from within itself.")
39 | failed.emit()
40 | return
41 |
42 | http_request.request(next_version_release.zipball_url)
43 | download_button.disabled = true
44 | download_button.text = DialogueConstants.translate(&"update.downloading")
45 |
46 |
47 | func _on_http_request_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void:
48 | if result != HTTPRequest.RESULT_SUCCESS:
49 | failed.emit()
50 | return
51 |
52 | # Save the downloaded zip
53 | var zip_file: FileAccess = FileAccess.open(TEMP_FILE_NAME, FileAccess.WRITE)
54 | zip_file.store_buffer(body)
55 | zip_file.close()
56 |
57 | OS.move_to_trash(ProjectSettings.globalize_path("res://addons/dialogue_manager"))
58 |
59 | var zip_reader: ZIPReader = ZIPReader.new()
60 | zip_reader.open(TEMP_FILE_NAME)
61 | var files: PackedStringArray = zip_reader.get_files()
62 |
63 | var base_path = files[1]
64 | # Remove archive folder
65 | files.remove_at(0)
66 | # Remove assets folder
67 | files.remove_at(0)
68 |
69 | for path in files:
70 | var new_file_path: String = path.replace(base_path, "")
71 | if path.ends_with("/"):
72 | DirAccess.make_dir_recursive_absolute("res://addons/%s" % new_file_path)
73 | else:
74 | var file: FileAccess = FileAccess.open("res://addons/%s" % new_file_path, FileAccess.WRITE)
75 | file.store_buffer(zip_reader.read_file(path))
76 |
77 | zip_reader.close()
78 | DirAccess.remove_absolute(TEMP_FILE_NAME)
79 |
80 | updated.emit(next_version_release.tag_name.substr(1))
81 |
82 |
83 | func _on_notes_button_pressed() -> void:
84 | OS.shell_open(next_version_release.html_url)
85 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/download_update_panel.gd.uid:
--------------------------------------------------------------------------------
1 | uid://kpwo418lb2t2
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/download_update_panel.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=3 format=3 uid="uid://qdxrxv3c3hxk"]
2 |
3 | [ext_resource type="Script" uid="uid://kpwo418lb2t2" path="res://addons/dialogue_manager/components/download_update_panel.gd" id="1_4tm1k"]
4 | [ext_resource type="Texture2D" uid="uid://d3baj6rygkb3f" path="res://addons/dialogue_manager/assets/update.svg" id="2_4o2m6"]
5 |
6 | [node name="DownloadUpdatePanel" type="Control"]
7 | layout_mode = 3
8 | anchors_preset = 15
9 | anchor_right = 1.0
10 | anchor_bottom = 1.0
11 | grow_horizontal = 2
12 | grow_vertical = 2
13 | script = ExtResource("1_4tm1k")
14 |
15 | [node name="HTTPRequest" type="HTTPRequest" parent="."]
16 |
17 | [node name="VBox" type="VBoxContainer" parent="."]
18 | layout_mode = 1
19 | anchors_preset = 15
20 | anchor_right = 1.0
21 | anchor_bottom = 1.0
22 | offset_left = -1.0
23 | offset_top = 9.0
24 | offset_right = -1.0
25 | offset_bottom = 9.0
26 | grow_horizontal = 2
27 | grow_vertical = 2
28 | theme_override_constants/separation = 10
29 |
30 | [node name="Logo" type="TextureRect" parent="VBox"]
31 | unique_name_in_owner = true
32 | clip_contents = true
33 | custom_minimum_size = Vector2(300, 80)
34 | layout_mode = 2
35 | texture = ExtResource("2_4o2m6")
36 | stretch_mode = 5
37 |
38 | [node name="Label" type="Label" parent="VBox"]
39 | layout_mode = 2
40 | text = "v1.2.3 is available for download."
41 | horizontal_alignment = 1
42 |
43 | [node name="Center" type="CenterContainer" parent="VBox"]
44 | layout_mode = 2
45 |
46 | [node name="DownloadButton" type="Button" parent="VBox/Center"]
47 | unique_name_in_owner = true
48 | layout_mode = 2
49 | text = "Download update"
50 |
51 | [node name="Center2" type="CenterContainer" parent="VBox"]
52 | layout_mode = 2
53 |
54 | [node name="NotesButton" type="LinkButton" parent="VBox/Center2"]
55 | layout_mode = 2
56 | text = "Read release notes"
57 |
58 | [connection signal="request_completed" from="HTTPRequest" to="." method="_on_http_request_request_completed"]
59 | [connection signal="pressed" from="VBox/Center/DownloadButton" to="." method="_on_download_button_pressed"]
60 | [connection signal="pressed" from="VBox/Center2/NotesButton" to="." method="_on_notes_button_pressed"]
61 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/editor_property/editor_property.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | extends EditorProperty
3 |
4 |
5 | const DialoguePropertyEditorControl = preload("./editor_property_control.tscn")
6 |
7 |
8 | var editor_plugin: EditorPlugin
9 |
10 | var control = DialoguePropertyEditorControl.instantiate()
11 | var current_value: Resource
12 | var is_updating: bool = false
13 |
14 |
15 | func _init() -> void:
16 | add_child(control)
17 |
18 | control.resource = current_value
19 |
20 | control.pressed.connect(_on_button_pressed)
21 | control.resource_changed.connect(_on_resource_changed)
22 |
23 |
24 | func _update_property() -> void:
25 | var next_value = get_edited_object()[get_edited_property()]
26 |
27 | # The resource might have been deleted elsewhere so check that it's not in a weird state
28 | if is_instance_valid(next_value) and not next_value.resource_path.ends_with(".dialogue"):
29 | emit_changed(get_edited_property(), null)
30 | return
31 |
32 | if next_value == current_value: return
33 |
34 | is_updating = true
35 | current_value = next_value
36 | control.resource = current_value
37 | is_updating = false
38 |
39 |
40 | ### Signals
41 |
42 |
43 | func _on_button_pressed() -> void:
44 | editor_plugin.edit(current_value)
45 |
46 |
47 | func _on_resource_changed(next_resource: Resource) -> void:
48 | emit_changed(get_edited_property(), next_resource)
49 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/editor_property/editor_property.gd.uid:
--------------------------------------------------------------------------------
1 | uid://nyypeje1a036
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/editor_property/editor_property_control.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | extends HBoxContainer
3 |
4 |
5 | signal pressed()
6 | signal resource_changed(next_resource: Resource)
7 |
8 |
9 | const ITEM_NEW = 100
10 | const ITEM_QUICK_LOAD = 200
11 | const ITEM_LOAD = 201
12 | const ITEM_EDIT = 300
13 | const ITEM_CLEAR = 301
14 | const ITEM_FILESYSTEM = 400
15 |
16 |
17 | @onready var button: Button = $ResourceButton
18 | @onready var menu_button: Button = $MenuButton
19 | @onready var menu: PopupMenu = $Menu
20 | @onready var quick_open_dialog: ConfirmationDialog = $QuickOpenDialog
21 | @onready var files_list = $QuickOpenDialog/FilesList
22 | @onready var new_dialog: FileDialog = $NewDialog
23 | @onready var open_dialog: FileDialog = $OpenDialog
24 |
25 | var editor_plugin: EditorPlugin
26 |
27 | var resource: Resource:
28 | set(next_resource):
29 | resource = next_resource
30 | if button:
31 | button.resource = resource
32 | get:
33 | return resource
34 |
35 | var is_waiting_for_file: bool = false
36 | var quick_selected_file: String = ""
37 |
38 |
39 | func _ready() -> void:
40 | menu_button.icon = get_theme_icon("GuiDropdown", "EditorIcons")
41 | editor_plugin = Engine.get_meta("DialogueManagerPlugin")
42 |
43 |
44 | func build_menu() -> void:
45 | menu.clear()
46 |
47 | menu.add_icon_item(editor_plugin._get_plugin_icon(), "New Dialogue", ITEM_NEW)
48 | menu.add_separator()
49 | menu.add_icon_item(get_theme_icon("Load", "EditorIcons"), "Quick Load", ITEM_QUICK_LOAD)
50 | menu.add_icon_item(get_theme_icon("Load", "EditorIcons"), "Load", ITEM_LOAD)
51 | if resource:
52 | menu.add_icon_item(get_theme_icon("Edit", "EditorIcons"), "Edit", ITEM_EDIT)
53 | menu.add_icon_item(get_theme_icon("Clear", "EditorIcons"), "Clear", ITEM_CLEAR)
54 | menu.add_separator()
55 | menu.add_item("Show in FileSystem", ITEM_FILESYSTEM)
56 |
57 | menu.size = Vector2.ZERO
58 |
59 |
60 | ### Signals
61 |
62 |
63 | func _on_new_dialog_file_selected(path: String) -> void:
64 | editor_plugin.main_view.new_file(path)
65 | is_waiting_for_file = false
66 | if Engine.get_meta("DMCache").has_file(path):
67 | resource_changed.emit(load(path))
68 | else:
69 | var next_resource: Resource = await editor_plugin.import_plugin.compiled_resource
70 | next_resource.resource_path = path
71 | resource_changed.emit(next_resource)
72 |
73 |
74 | func _on_open_dialog_file_selected(file: String) -> void:
75 | resource_changed.emit(load(file))
76 |
77 |
78 | func _on_file_dialog_canceled() -> void:
79 | is_waiting_for_file = false
80 |
81 |
82 | func _on_resource_button_pressed() -> void:
83 | if is_instance_valid(resource):
84 | EditorInterface.call_deferred("edit_resource", resource)
85 | else:
86 | build_menu()
87 | menu.position = get_viewport().position + Vector2i(
88 | button.global_position.x + button.size.x - menu.size.x,
89 | 2 + menu_button.global_position.y + button.size.y
90 | )
91 | menu.popup()
92 |
93 |
94 | func _on_resource_button_resource_dropped(next_resource: Resource) -> void:
95 | resource_changed.emit(next_resource)
96 |
97 |
98 | func _on_menu_button_pressed() -> void:
99 | build_menu()
100 | menu.position = get_viewport().position + Vector2i(
101 | menu_button.global_position.x + menu_button.size.x - menu.size.x,
102 | 2 + menu_button.global_position.y + menu_button.size.y
103 | )
104 | menu.popup()
105 |
106 |
107 | func _on_menu_id_pressed(id: int) -> void:
108 | match id:
109 | ITEM_NEW:
110 | is_waiting_for_file = true
111 | new_dialog.popup_centered()
112 |
113 | ITEM_QUICK_LOAD:
114 | quick_selected_file = ""
115 | files_list.files = Engine.get_meta("DMCache").get_files()
116 | if resource:
117 | files_list.select_file(resource.resource_path)
118 | quick_open_dialog.popup_centered()
119 | files_list.focus_filter()
120 |
121 | ITEM_LOAD:
122 | is_waiting_for_file = true
123 | open_dialog.popup_centered()
124 |
125 | ITEM_EDIT:
126 | EditorInterface.call_deferred("edit_resource", resource)
127 |
128 | ITEM_CLEAR:
129 | resource_changed.emit(null)
130 |
131 | ITEM_FILESYSTEM:
132 | var file_system = EditorInterface.get_file_system_dock()
133 | file_system.navigate_to_path(resource.resource_path)
134 |
135 |
136 | func _on_files_list_file_double_clicked(file_path: String) -> void:
137 | resource_changed.emit(load(file_path))
138 | quick_open_dialog.hide()
139 |
140 |
141 | func _on_files_list_file_selected(file_path: String) -> void:
142 | quick_selected_file = file_path
143 |
144 |
145 | func _on_quick_open_dialog_confirmed() -> void:
146 | if quick_selected_file != "":
147 | resource_changed.emit(load(quick_selected_file))
148 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/editor_property/editor_property_control.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dooe2pflnqtve
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/editor_property/editor_property_control.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=4 format=3 uid="uid://ycn6uaj7dsrh"]
2 |
3 | [ext_resource type="Script" uid="uid://dooe2pflnqtve" path="res://addons/dialogue_manager/components/editor_property/editor_property_control.gd" id="1_het12"]
4 | [ext_resource type="PackedScene" uid="uid://b16uuqjuof3n5" path="res://addons/dialogue_manager/components/editor_property/resource_button.tscn" id="2_hh3d4"]
5 | [ext_resource type="PackedScene" uid="uid://dnufpcdrreva3" path="res://addons/dialogue_manager/components/files_list.tscn" id="3_l8fp6"]
6 |
7 | [node name="PropertyEditorButton" type="HBoxContainer"]
8 | offset_right = 40.0
9 | offset_bottom = 40.0
10 | size_flags_horizontal = 3
11 | theme_override_constants/separation = 0
12 | script = ExtResource("1_het12")
13 |
14 | [node name="ResourceButton" parent="." instance=ExtResource("2_hh3d4")]
15 | layout_mode = 2
16 | text = ""
17 | text_overrun_behavior = 3
18 | clip_text = true
19 |
20 | [node name="MenuButton" type="Button" parent="."]
21 | layout_mode = 2
22 |
23 | [node name="Menu" type="PopupMenu" parent="."]
24 |
25 | [node name="QuickOpenDialog" type="ConfirmationDialog" parent="."]
26 | title = "Find Dialogue Resource"
27 | size = Vector2i(400, 600)
28 | min_size = Vector2i(400, 600)
29 | ok_button_text = "Open"
30 |
31 | [node name="FilesList" parent="QuickOpenDialog" instance=ExtResource("3_l8fp6")]
32 |
33 | [node name="NewDialog" type="FileDialog" parent="."]
34 | size = Vector2i(900, 750)
35 | min_size = Vector2i(900, 750)
36 | dialog_hide_on_ok = true
37 | filters = PackedStringArray("*.dialogue ; Dialogue")
38 |
39 | [node name="OpenDialog" type="FileDialog" parent="."]
40 | title = "Open a File"
41 | size = Vector2i(900, 750)
42 | min_size = Vector2i(900, 750)
43 | ok_button_text = "Open"
44 | dialog_hide_on_ok = true
45 | file_mode = 0
46 | filters = PackedStringArray("*.dialogue ; Dialogue")
47 |
48 | [connection signal="pressed" from="ResourceButton" to="." method="_on_resource_button_pressed"]
49 | [connection signal="resource_dropped" from="ResourceButton" to="." method="_on_resource_button_resource_dropped"]
50 | [connection signal="pressed" from="MenuButton" to="." method="_on_menu_button_pressed"]
51 | [connection signal="id_pressed" from="Menu" to="." method="_on_menu_id_pressed"]
52 | [connection signal="confirmed" from="QuickOpenDialog" to="." method="_on_quick_open_dialog_confirmed"]
53 | [connection signal="file_double_clicked" from="QuickOpenDialog/FilesList" to="." method="_on_files_list_file_double_clicked"]
54 | [connection signal="file_selected" from="QuickOpenDialog/FilesList" to="." method="_on_files_list_file_selected"]
55 | [connection signal="canceled" from="NewDialog" to="." method="_on_file_dialog_canceled"]
56 | [connection signal="file_selected" from="NewDialog" to="." method="_on_new_dialog_file_selected"]
57 | [connection signal="canceled" from="OpenDialog" to="." method="_on_file_dialog_canceled"]
58 | [connection signal="file_selected" from="OpenDialog" to="." method="_on_open_dialog_file_selected"]
59 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/editor_property/resource_button.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | extends Button
3 |
4 |
5 | signal resource_dropped(next_resource: Resource)
6 |
7 |
8 | var resource: Resource:
9 | set(next_resource):
10 | resource = next_resource
11 | if resource:
12 | icon = Engine.get_meta("DialogueManagerPlugin")._get_plugin_icon()
13 | text = resource.resource_path.get_file().replace(".dialogue", "")
14 | else:
15 | icon = null
16 | text = ""
17 | get:
18 | return resource
19 |
20 |
21 | func _notification(what: int) -> void:
22 | match what:
23 | NOTIFICATION_DRAG_BEGIN:
24 | var data = get_viewport().gui_get_drag_data()
25 | if typeof(data) == TYPE_DICTIONARY and data.type == "files" and data.files.size() > 0 and data.files[0].ends_with(".dialogue"):
26 | add_theme_stylebox_override("normal", get_theme_stylebox("focus", "LineEdit"))
27 | add_theme_stylebox_override("hover", get_theme_stylebox("focus", "LineEdit"))
28 |
29 | NOTIFICATION_DRAG_END:
30 | self.resource = resource
31 | remove_theme_stylebox_override("normal")
32 | remove_theme_stylebox_override("hover")
33 |
34 |
35 | func _can_drop_data(at_position: Vector2, data) -> bool:
36 | if typeof(data) != TYPE_DICTIONARY: return false
37 | if data.type != "files": return false
38 |
39 | var files: PackedStringArray = Array(data.files).filter(func(f): return f.get_extension() == "dialogue")
40 | return files.size() > 0
41 |
42 |
43 | func _drop_data(at_position: Vector2, data) -> void:
44 | var files: PackedStringArray = Array(data.files).filter(func(f): return f.get_extension() == "dialogue")
45 |
46 | if files.size() == 0: return
47 |
48 | resource_dropped.emit(load(files[0]))
49 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/editor_property/resource_button.gd.uid:
--------------------------------------------------------------------------------
1 | uid://damhqta55t67c
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/editor_property/resource_button.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=2 format=3 uid="uid://b16uuqjuof3n5"]
2 |
3 | [ext_resource type="Script" uid="uid://damhqta55t67c" path="res://addons/dialogue_manager/components/editor_property/resource_button.gd" id="1_7u2i7"]
4 |
5 | [node name="ResourceButton" type="Button"]
6 | offset_right = 8.0
7 | offset_bottom = 8.0
8 | size_flags_horizontal = 3
9 | script = ExtResource("1_7u2i7")
10 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/errors_panel.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | extends HBoxContainer
3 |
4 |
5 | signal error_pressed(line_number)
6 |
7 |
8 | const DialogueConstants = preload("../constants.gd")
9 |
10 |
11 | @onready var error_button: Button = $ErrorButton
12 | @onready var next_button: Button = $NextButton
13 | @onready var count_label: Label = $CountLabel
14 | @onready var previous_button: Button = $PreviousButton
15 |
16 | ## The index of the current error being shown
17 | var error_index: int = 0:
18 | set(next_error_index):
19 | error_index = wrap(next_error_index, 0, errors.size())
20 | show_error()
21 | get:
22 | return error_index
23 |
24 | ## The list of all errors
25 | var errors: Array = []:
26 | set(next_errors):
27 | errors = next_errors
28 | self.error_index = 0
29 | get:
30 | return errors
31 |
32 |
33 | func _ready() -> void:
34 | apply_theme()
35 | hide()
36 |
37 |
38 | ## Set up colors and icons
39 | func apply_theme() -> void:
40 | error_button.add_theme_color_override("font_color", get_theme_color("error_color", "Editor"))
41 | error_button.add_theme_color_override("font_hover_color", get_theme_color("error_color", "Editor"))
42 | error_button.icon = get_theme_icon("StatusError", "EditorIcons")
43 | previous_button.icon = get_theme_icon("ArrowLeft", "EditorIcons")
44 | next_button.icon = get_theme_icon("ArrowRight", "EditorIcons")
45 |
46 |
47 | ## Move the error index to match a given line
48 | func show_error_for_line_number(line_number: int) -> void:
49 | for i in range(0, errors.size()):
50 | if errors[i].line_number == line_number:
51 | self.error_index = i
52 |
53 |
54 | ## Show the current error
55 | func show_error() -> void:
56 | if errors.size() == 0:
57 | hide()
58 | else:
59 | show()
60 | count_label.text = DialogueConstants.translate(&"n_of_n").format({ index = error_index + 1, total = errors.size() })
61 | var error = errors[error_index]
62 | 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) })
63 | if error.has("external_error"):
64 | error_button.text += " " + DialogueConstants.get_error_message(error.external_error)
65 |
66 |
67 | ### Signals
68 |
69 |
70 | func _on_errors_panel_theme_changed() -> void:
71 | apply_theme()
72 |
73 |
74 | func _on_error_button_pressed() -> void:
75 | error_pressed.emit(errors[error_index].line_number, errors[error_index].column_number)
76 |
77 |
78 | func _on_previous_button_pressed() -> void:
79 | self.error_index -= 1
80 | _on_error_button_pressed()
81 |
82 |
83 | func _on_next_button_pressed() -> void:
84 | self.error_index += 1
85 | _on_error_button_pressed()
86 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/errors_panel.gd.uid:
--------------------------------------------------------------------------------
1 | uid://d2l8nlb6hhrfp
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/errors_panel.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=4 format=3 uid="uid://cs8pwrxr5vxix"]
2 |
3 | [ext_resource type="Script" uid="uid://d2l8nlb6hhrfp" path="res://addons/dialogue_manager/components/errors_panel.gd" id="1_nfm3c"]
4 |
5 | [sub_resource type="Image" id="Image_w0gko"]
6 | data = {
7 | "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),
8 | "format": "RGBA8",
9 | "height": 16,
10 | "mipmaps": false,
11 | "width": 16
12 | }
13 |
14 | [sub_resource type="ImageTexture" id="ImageTexture_s6fxl"]
15 | image = SubResource("Image_w0gko")
16 |
17 | [node name="ErrorsPanel" type="HBoxContainer"]
18 | visible = false
19 | offset_right = 1024.0
20 | offset_bottom = 600.0
21 | grow_horizontal = 2
22 | grow_vertical = 2
23 | script = ExtResource("1_nfm3c")
24 | metadata/_edit_layout_mode = 1
25 |
26 | [node name="ErrorButton" type="Button" parent="."]
27 | layout_mode = 2
28 | size_flags_horizontal = 3
29 | theme_override_colors/font_color = Color(0, 0, 0, 1)
30 | theme_override_colors/font_hover_color = Color(0, 0, 0, 1)
31 | theme_override_constants/h_separation = 3
32 | icon = SubResource("ImageTexture_s6fxl")
33 | flat = true
34 | alignment = 0
35 | text_overrun_behavior = 4
36 |
37 | [node name="Spacer" type="Control" parent="."]
38 | custom_minimum_size = Vector2(40, 0)
39 | layout_mode = 2
40 |
41 | [node name="PreviousButton" type="Button" parent="."]
42 | layout_mode = 2
43 | icon = SubResource("ImageTexture_s6fxl")
44 | flat = true
45 |
46 | [node name="CountLabel" type="Label" parent="."]
47 | layout_mode = 2
48 |
49 | [node name="NextButton" type="Button" parent="."]
50 | layout_mode = 2
51 | icon = SubResource("ImageTexture_s6fxl")
52 | flat = true
53 |
54 | [connection signal="pressed" from="ErrorButton" to="." method="_on_error_button_pressed"]
55 | [connection signal="pressed" from="PreviousButton" to="." method="_on_previous_button_pressed"]
56 | [connection signal="pressed" from="NextButton" to="." method="_on_next_button_pressed"]
57 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/files_list.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | extends VBoxContainer
3 |
4 |
5 | signal file_selected(file_path: String)
6 | signal file_popup_menu_requested(at_position: Vector2)
7 | signal file_double_clicked(file_path: String)
8 | signal file_middle_clicked(file_path: String)
9 |
10 |
11 | const DialogueConstants = preload("../constants.gd")
12 |
13 | const MODIFIED_SUFFIX = "(*)"
14 |
15 |
16 | @export var icon: Texture2D
17 |
18 | @onready var filter_edit: LineEdit = $FilterEdit
19 | @onready var list: ItemList = $List
20 |
21 | var file_map: Dictionary = {}
22 |
23 | var current_file_path: String = ""
24 | var last_selected_file_path: String = ""
25 |
26 | var files: PackedStringArray = []:
27 | set(next_files):
28 | files = next_files
29 | files.sort()
30 | update_file_map()
31 | apply_filter()
32 | get:
33 | return files
34 |
35 | var unsaved_files: Array[String] = []
36 |
37 | var filter: String = "":
38 | set(next_filter):
39 | filter = next_filter
40 | apply_filter()
41 | get:
42 | return filter
43 |
44 |
45 | func _ready() -> void:
46 | apply_theme()
47 |
48 | filter_edit.placeholder_text = DialogueConstants.translate(&"files_list.filter")
49 |
50 |
51 | func focus_filter() -> void:
52 | filter_edit.grab_focus()
53 |
54 |
55 | func select_file(file: String) -> void:
56 | list.deselect_all()
57 | for i in range(0, list.get_item_count()):
58 | var item_text = list.get_item_text(i).replace(MODIFIED_SUFFIX, "")
59 | if item_text == get_nice_file(file, item_text.count("/") + 1):
60 | list.select(i)
61 | last_selected_file_path = file
62 |
63 |
64 | func mark_file_as_unsaved(file: String, is_unsaved: bool) -> void:
65 | if not file in unsaved_files and is_unsaved:
66 | unsaved_files.append(file)
67 | elif file in unsaved_files and not is_unsaved:
68 | unsaved_files.erase(file)
69 | apply_filter()
70 |
71 |
72 | func update_file_map() -> void:
73 | file_map = {}
74 | for file in files:
75 | var nice_file: String = get_nice_file(file)
76 |
77 | # See if a value with just the file name is already in the map
78 | for key in file_map.keys():
79 | if file_map[key] == nice_file:
80 | var bit_count = nice_file.count("/") + 2
81 |
82 | var existing_nice_file = get_nice_file(key, bit_count)
83 | nice_file = get_nice_file(file, bit_count)
84 |
85 | while nice_file == existing_nice_file:
86 | bit_count += 1
87 | existing_nice_file = get_nice_file(key, bit_count)
88 | nice_file = get_nice_file(file, bit_count)
89 |
90 | file_map[key] = existing_nice_file
91 |
92 | file_map[file] = nice_file
93 |
94 |
95 | func get_nice_file(file_path: String, path_bit_count: int = 1) -> String:
96 | var bits = file_path.replace("res://", "").replace(".dialogue", "").split("/")
97 | bits = bits.slice(-path_bit_count)
98 | return "/".join(bits)
99 |
100 |
101 | func apply_filter() -> void:
102 | list.clear()
103 | for file in file_map.keys():
104 | if filter == "" or filter.to_lower() in file.to_lower():
105 | var nice_file = file_map[file]
106 | if file in unsaved_files:
107 | nice_file += MODIFIED_SUFFIX
108 | var new_id := list.add_item(nice_file)
109 | list.set_item_icon(new_id, icon)
110 |
111 | select_file(current_file_path)
112 |
113 |
114 | func apply_theme() -> void:
115 | if is_instance_valid(filter_edit):
116 | filter_edit.right_icon = get_theme_icon("Search", "EditorIcons")
117 | if is_instance_valid(list):
118 | list.add_theme_stylebox_override("panel", get_theme_stylebox("panel", "Panel"))
119 |
120 |
121 | ### Signals
122 |
123 |
124 | func _on_theme_changed() -> void:
125 | apply_theme()
126 |
127 |
128 | func _on_filter_edit_text_changed(new_text: String) -> void:
129 | self.filter = new_text
130 |
131 |
132 | func _on_list_item_clicked(index: int, at_position: Vector2, mouse_button_index: int) -> void:
133 | var item_text = list.get_item_text(index).replace(MODIFIED_SUFFIX, "")
134 | var file = file_map.find_key(item_text)
135 |
136 | if mouse_button_index == MOUSE_BUTTON_LEFT or mouse_button_index == MOUSE_BUTTON_RIGHT:
137 | select_file(file)
138 | file_selected.emit(file)
139 | if mouse_button_index == MOUSE_BUTTON_RIGHT:
140 | file_popup_menu_requested.emit(at_position)
141 |
142 | if mouse_button_index == MOUSE_BUTTON_MIDDLE:
143 | file_middle_clicked.emit(file)
144 |
145 |
146 | func _on_list_item_activated(index: int) -> void:
147 | var item_text = list.get_item_text(index).replace(MODIFIED_SUFFIX, "")
148 | var file = file_map.find_key(item_text)
149 | select_file(file)
150 | file_double_clicked.emit(file)
151 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/files_list.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dqa4a4wwoo0aa
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/files_list.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=3 format=3 uid="uid://dnufpcdrreva3"]
2 |
3 | [ext_resource type="Script" uid="uid://dqa4a4wwoo0aa" path="res://addons/dialogue_manager/components/files_list.gd" id="1_cytii"]
4 | [ext_resource type="Texture2D" uid="uid://d3lr2uas6ax8v" path="res://addons/dialogue_manager/assets/icon.svg" id="2_3ijx1"]
5 |
6 | [node name="FilesList" type="VBoxContainer"]
7 | anchors_preset = 15
8 | anchor_right = 1.0
9 | anchor_bottom = 1.0
10 | grow_horizontal = 2
11 | grow_vertical = 2
12 | size_flags_vertical = 3
13 | script = ExtResource("1_cytii")
14 | icon = ExtResource("2_3ijx1")
15 |
16 | [node name="FilterEdit" type="LineEdit" parent="."]
17 | layout_mode = 2
18 | placeholder_text = "Filter files"
19 | clear_button_enabled = true
20 |
21 | [node name="List" type="ItemList" parent="."]
22 | layout_mode = 2
23 | size_flags_vertical = 3
24 | allow_rmb_select = true
25 |
26 | [connection signal="theme_changed" from="." to="." method="_on_theme_changed"]
27 | [connection signal="text_changed" from="FilterEdit" to="." method="_on_filter_edit_text_changed"]
28 | [connection signal="item_activated" from="List" to="." method="_on_list_item_activated"]
29 | [connection signal="item_clicked" from="List" to="." method="_on_list_item_clicked"]
30 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/find_in_files.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | extends Control
3 |
4 | signal result_selected(path: String, cursor: Vector2, length: int)
5 |
6 |
7 | const DialogueConstants = preload("../constants.gd")
8 |
9 |
10 | @export var main_view: Control
11 | @export var code_edit: CodeEdit
12 |
13 | @onready var input: LineEdit = %Input
14 | @onready var search_button: Button = %SearchButton
15 | @onready var match_case_button: CheckBox = %MatchCaseButton
16 | @onready var replace_toggle: CheckButton = %ReplaceToggle
17 | @onready var replace_container: VBoxContainer = %ReplaceContainer
18 | @onready var replace_input: LineEdit = %ReplaceInput
19 | @onready var replace_selected_button: Button = %ReplaceSelectedButton
20 | @onready var replace_all_button: Button = %ReplaceAllButton
21 | @onready var results_container: VBoxContainer = %ResultsContainer
22 | @onready var result_template: HBoxContainer = %ResultTemplate
23 |
24 | var current_results: Dictionary = {}:
25 | set(value):
26 | current_results = value
27 | update_results_view()
28 | if current_results.size() == 0:
29 | replace_selected_button.disabled = true
30 | replace_all_button.disabled = true
31 | else:
32 | replace_selected_button.disabled = false
33 | replace_all_button.disabled = false
34 | get:
35 | return current_results
36 |
37 | var selections: PackedStringArray = []
38 |
39 |
40 | func prepare() -> void:
41 | input.grab_focus()
42 |
43 | var template_label = result_template.get_node("Label")
44 | template_label.get_theme_stylebox(&"focus").bg_color = code_edit.theme_overrides.current_line_color
45 | template_label.add_theme_font_override(&"normal_font", code_edit.get_theme_font(&"font"))
46 |
47 | replace_toggle.set_pressed_no_signal(false)
48 | replace_container.hide()
49 |
50 | $VBoxContainer/HBoxContainer/FindContainer/Label.text = DialogueConstants.translate(&"search.find")
51 | input.placeholder_text = DialogueConstants.translate(&"search.placeholder")
52 | input.text = ""
53 | search_button.text = DialogueConstants.translate(&"search.find_all")
54 | match_case_button.text = DialogueConstants.translate(&"search.match_case")
55 | replace_toggle.text = DialogueConstants.translate(&"search.toggle_replace")
56 | $VBoxContainer/HBoxContainer/ReplaceContainer/ReplaceLabel.text = DialogueConstants.translate(&"search.replace_with")
57 | replace_input.placeholder_text = DialogueConstants.translate(&"search.replace_placeholder")
58 | replace_input.text = ""
59 | replace_all_button.text = DialogueConstants.translate(&"search.replace_all")
60 | replace_selected_button.text = DialogueConstants.translate(&"search.replace_selected")
61 |
62 | selections.clear()
63 | self.current_results = {}
64 |
65 | #region helpers
66 |
67 |
68 | func update_results_view() -> void:
69 | for child in results_container.get_children():
70 | child.queue_free()
71 |
72 | for path in current_results.keys():
73 | var path_label: Label = Label.new()
74 | path_label.text = path
75 | # Show open files
76 | if main_view.open_buffers.has(path):
77 | path_label.text += "(*)"
78 | results_container.add_child(path_label)
79 | for path_result in current_results.get(path):
80 | var result_item: HBoxContainer = result_template.duplicate()
81 |
82 | var checkbox: CheckBox = result_item.get_node("CheckBox") as CheckBox
83 | var key: String = get_selection_key(path, path_result)
84 | checkbox.toggled.connect(func(is_pressed):
85 | if is_pressed:
86 | if not selections.has(key):
87 | selections.append(key)
88 | else:
89 | if selections.has(key):
90 | selections.remove_at(selections.find(key))
91 | )
92 | checkbox.set_pressed_no_signal(selections.has(key))
93 | checkbox.visible = replace_toggle.button_pressed
94 |
95 | var result_label: RichTextLabel = result_item.get_node("Label") as RichTextLabel
96 | var colors: Dictionary = code_edit.theme_overrides
97 | var highlight: String = ""
98 | if replace_toggle.button_pressed:
99 | var matched_word: String = "[bgcolor=" + colors.critical_color.to_html() + "][color=" + colors.text_color.to_html() + "]" + path_result.matched_text + "[/color][/bgcolor]"
100 | highlight = "[s]" + matched_word + "[/s][bgcolor=" + colors.notice_color.to_html() + "][color=" + colors.text_color.to_html() + "]" + replace_input.text + "[/color][/bgcolor]"
101 | else:
102 | highlight = "[bgcolor=" + colors.notice_color.to_html() + "][color=" + colors.text_color.to_html() + "]" + path_result.matched_text + "[/color][/bgcolor]"
103 | var text: String = path_result.text.substr(0, path_result.index) + highlight + path_result.text.substr(path_result.index + path_result.query.length())
104 | result_label.text = "%s: %s" % [str(path_result.line).lpad(4), text]
105 | result_label.gui_input.connect(func(event):
106 | if event is InputEventMouseButton and (event as InputEventMouseButton).button_index == MOUSE_BUTTON_LEFT and (event as InputEventMouseButton).double_click:
107 | result_selected.emit(path, Vector2(path_result.index, path_result.line), path_result.query.length())
108 | )
109 |
110 | results_container.add_child(result_item)
111 |
112 |
113 | func find_in_files() -> Dictionary:
114 | var results: Dictionary = {}
115 |
116 | var q: String = input.text
117 | var cache = Engine.get_meta("DMCache")
118 | var file: FileAccess
119 | for path in cache.get_files():
120 | var path_results: Array = []
121 | var lines: PackedStringArray = []
122 |
123 | if main_view.open_buffers.has(path):
124 | lines = main_view.open_buffers.get(path).text.split("\n")
125 | else:
126 | file = FileAccess.open(path, FileAccess.READ)
127 | lines = file.get_as_text().split("\n")
128 |
129 | for i in range(0, lines.size()):
130 | var index: int = find_in_line(lines[i], q)
131 | while index > -1:
132 | path_results.append({
133 | line = i,
134 | index = index,
135 | text = lines[i],
136 | matched_text = lines[i].substr(index, q.length()),
137 | query = q
138 | })
139 | index = find_in_line(lines[i], q, index + q.length())
140 |
141 | if file != null and file.is_open():
142 | file.close()
143 |
144 | if path_results.size() > 0:
145 | results[path] = path_results
146 |
147 | return results
148 |
149 |
150 | func get_selection_key(path: String, path_result: Dictionary) -> String:
151 | return "%s-%d-%d" % [path, path_result.line, path_result.index]
152 |
153 |
154 | func find_in_line(line: String, query: String, from_index: int = 0) -> int:
155 | if match_case_button.button_pressed:
156 | return line.find(query, from_index)
157 | else:
158 | return line.findn(query, from_index)
159 |
160 |
161 | func replace_results(only_selected: bool) -> void:
162 | var file: FileAccess
163 | var lines: PackedStringArray = []
164 | for path in current_results:
165 | if main_view.open_buffers.has(path):
166 | lines = main_view.open_buffers.get(path).text.split("\n")
167 | else:
168 | file = FileAccess.open(path, FileAccess.READ_WRITE)
169 | lines = file.get_as_text().split("\n")
170 |
171 | # Read the results in reverse because we're going to be modifying them as we go
172 | var path_results: Array = current_results.get(path).duplicate()
173 | path_results.reverse()
174 | for path_result in path_results:
175 | var key: String = get_selection_key(path, path_result)
176 | if not only_selected or (only_selected and selections.has(key)):
177 | lines[path_result.line] = lines[path_result.line].substr(0, path_result.index) + replace_input.text + lines[path_result.line].substr(path_result.index + path_result.matched_text.length())
178 |
179 | var replaced_text: String = "\n".join(lines)
180 | if file != null and file.is_open():
181 | file.seek(0)
182 | file.store_string(replaced_text)
183 | file.close()
184 | else:
185 | main_view.open_buffers.get(path).text = replaced_text
186 | if main_view.current_file_path == path:
187 | code_edit.text = replaced_text
188 |
189 | current_results = find_in_files()
190 |
191 |
192 | #endregion
193 |
194 | #region signals
195 |
196 |
197 | func _on_search_button_pressed() -> void:
198 | selections.clear()
199 | self.current_results = find_in_files()
200 |
201 |
202 | func _on_input_text_submitted(new_text: String) -> void:
203 | _on_search_button_pressed()
204 |
205 |
206 | func _on_replace_toggle_toggled(toggled_on: bool) -> void:
207 | replace_container.visible = toggled_on
208 | if toggled_on:
209 | replace_input.grab_focus()
210 | update_results_view()
211 |
212 |
213 | func _on_replace_input_text_changed(new_text: String) -> void:
214 | update_results_view()
215 |
216 |
217 | func _on_replace_selected_button_pressed() -> void:
218 | replace_results(true)
219 |
220 |
221 | func _on_replace_all_button_pressed() -> void:
222 | replace_results(false)
223 |
224 |
225 | func _on_match_case_button_toggled(toggled_on: bool) -> void:
226 | _on_search_button_pressed()
227 |
228 |
229 | #endregion
230 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/find_in_files.gd.uid:
--------------------------------------------------------------------------------
1 | uid://q368fmxxa8sd
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/find_in_files.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=3 format=3 uid="uid://0n7hwviyyly4"]
2 |
3 | [ext_resource type="Script" uid="uid://q368fmxxa8sd" path="res://addons/dialogue_manager/components/find_in_files.gd" id="1_3xicy"]
4 |
5 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_owohg"]
6 | bg_color = Color(0.266667, 0.278431, 0.352941, 0.243137)
7 | corner_detail = 1
8 |
9 | [node name="FindInFiles" type="Control"]
10 | layout_mode = 3
11 | anchors_preset = 15
12 | anchor_right = 1.0
13 | anchor_bottom = 1.0
14 | grow_horizontal = 2
15 | grow_vertical = 2
16 | size_flags_horizontal = 3
17 | size_flags_vertical = 3
18 | script = ExtResource("1_3xicy")
19 |
20 | [node name="VBoxContainer" type="VBoxContainer" parent="."]
21 | layout_mode = 1
22 | anchors_preset = 15
23 | anchor_right = 1.0
24 | anchor_bottom = 1.0
25 | grow_horizontal = 2
26 | grow_vertical = 2
27 |
28 | [node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
29 | layout_mode = 2
30 |
31 | [node name="FindContainer" type="VBoxContainer" parent="VBoxContainer/HBoxContainer"]
32 | layout_mode = 2
33 | size_flags_horizontal = 3
34 |
35 | [node name="Label" type="Label" parent="VBoxContainer/HBoxContainer/FindContainer"]
36 | layout_mode = 2
37 | text = "Find:"
38 |
39 | [node name="Input" type="LineEdit" parent="VBoxContainer/HBoxContainer/FindContainer"]
40 | unique_name_in_owner = true
41 | layout_mode = 2
42 | clear_button_enabled = true
43 |
44 | [node name="FindToolbar" type="HBoxContainer" parent="VBoxContainer/HBoxContainer/FindContainer"]
45 | layout_mode = 2
46 |
47 | [node name="SearchButton" type="Button" parent="VBoxContainer/HBoxContainer/FindContainer/FindToolbar"]
48 | unique_name_in_owner = true
49 | layout_mode = 2
50 | text = "Find all..."
51 |
52 | [node name="MatchCaseButton" type="CheckBox" parent="VBoxContainer/HBoxContainer/FindContainer/FindToolbar"]
53 | unique_name_in_owner = true
54 | layout_mode = 2
55 | text = "Match case"
56 |
57 | [node name="Control" type="Control" parent="VBoxContainer/HBoxContainer/FindContainer/FindToolbar"]
58 | layout_mode = 2
59 | size_flags_horizontal = 3
60 |
61 | [node name="ReplaceToggle" type="CheckButton" parent="VBoxContainer/HBoxContainer/FindContainer/FindToolbar"]
62 | unique_name_in_owner = true
63 | layout_mode = 2
64 | text = "Replace"
65 |
66 | [node name="ReplaceContainer" type="VBoxContainer" parent="VBoxContainer/HBoxContainer"]
67 | unique_name_in_owner = true
68 | layout_mode = 2
69 | size_flags_horizontal = 3
70 |
71 | [node name="ReplaceLabel" type="Label" parent="VBoxContainer/HBoxContainer/ReplaceContainer"]
72 | layout_mode = 2
73 | text = "Replace with:"
74 |
75 | [node name="ReplaceInput" type="LineEdit" parent="VBoxContainer/HBoxContainer/ReplaceContainer"]
76 | unique_name_in_owner = true
77 | layout_mode = 2
78 | size_flags_horizontal = 3
79 | clear_button_enabled = true
80 |
81 | [node name="ReplaceToolbar" type="HBoxContainer" parent="VBoxContainer/HBoxContainer/ReplaceContainer"]
82 | layout_mode = 2
83 |
84 | [node name="ReplaceSelectedButton" type="Button" parent="VBoxContainer/HBoxContainer/ReplaceContainer/ReplaceToolbar"]
85 | unique_name_in_owner = true
86 | layout_mode = 2
87 | text = "Replace selected"
88 |
89 | [node name="ReplaceAllButton" type="Button" parent="VBoxContainer/HBoxContainer/ReplaceContainer/ReplaceToolbar"]
90 | unique_name_in_owner = true
91 | layout_mode = 2
92 | text = "Replace all"
93 |
94 | [node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer"]
95 | layout_mode = 2
96 |
97 | [node name="ReplaceToolbar" type="HBoxContainer" parent="VBoxContainer/VBoxContainer"]
98 | layout_mode = 2
99 |
100 | [node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer"]
101 | layout_mode = 2
102 | size_flags_vertical = 3
103 | follow_focus = true
104 |
105 | [node name="ResultsContainer" type="VBoxContainer" parent="VBoxContainer/ScrollContainer"]
106 | unique_name_in_owner = true
107 | layout_mode = 2
108 | size_flags_horizontal = 3
109 | size_flags_vertical = 3
110 | theme_override_constants/separation = 0
111 |
112 | [node name="ResultTemplate" type="HBoxContainer" parent="."]
113 | unique_name_in_owner = true
114 | layout_mode = 0
115 | offset_left = 155.0
116 | offset_top = -74.0
117 | offset_right = 838.0
118 | offset_bottom = -51.0
119 |
120 | [node name="CheckBox" type="CheckBox" parent="ResultTemplate"]
121 | layout_mode = 2
122 |
123 | [node name="Label" type="RichTextLabel" parent="ResultTemplate"]
124 | layout_mode = 2
125 | size_flags_horizontal = 3
126 | focus_mode = 2
127 | theme_override_styles/focus = SubResource("StyleBoxFlat_owohg")
128 | bbcode_enabled = true
129 | text = "Result"
130 | fit_content = true
131 | scroll_active = false
132 |
133 | [connection signal="text_submitted" from="VBoxContainer/HBoxContainer/FindContainer/Input" to="." method="_on_input_text_submitted"]
134 | [connection signal="pressed" from="VBoxContainer/HBoxContainer/FindContainer/FindToolbar/SearchButton" to="." method="_on_search_button_pressed"]
135 | [connection signal="toggled" from="VBoxContainer/HBoxContainer/FindContainer/FindToolbar/MatchCaseButton" to="." method="_on_match_case_button_toggled"]
136 | [connection signal="toggled" from="VBoxContainer/HBoxContainer/FindContainer/FindToolbar/ReplaceToggle" to="." method="_on_replace_toggle_toggled"]
137 | [connection signal="text_changed" from="VBoxContainer/HBoxContainer/ReplaceContainer/ReplaceInput" to="." method="_on_replace_input_text_changed"]
138 | [connection signal="pressed" from="VBoxContainer/HBoxContainer/ReplaceContainer/ReplaceToolbar/ReplaceSelectedButton" to="." method="_on_replace_selected_button_pressed"]
139 | [connection signal="pressed" from="VBoxContainer/HBoxContainer/ReplaceContainer/ReplaceToolbar/ReplaceAllButton" to="." method="_on_replace_all_button_pressed"]
140 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/search_and_replace.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | extends VBoxContainer
3 |
4 |
5 | signal open_requested()
6 | signal close_requested()
7 |
8 |
9 | const DialogueConstants = preload("../constants.gd")
10 |
11 |
12 | @onready var input: LineEdit = $Search/Input
13 | @onready var result_label: Label = $Search/ResultLabel
14 | @onready var previous_button: Button = $Search/PreviousButton
15 | @onready var next_button: Button = $Search/NextButton
16 | @onready var match_case_button: CheckBox = $Search/MatchCaseCheckBox
17 | @onready var replace_check_button: CheckButton = $Search/ReplaceCheckButton
18 | @onready var replace_panel: HBoxContainer = $Replace
19 | @onready var replace_input: LineEdit = $Replace/Input
20 | @onready var replace_button: Button = $Replace/ReplaceButton
21 | @onready var replace_all_button: Button = $Replace/ReplaceAllButton
22 |
23 | # The code edit we will be affecting (for some reason exporting this didn't work)
24 | var code_edit: CodeEdit:
25 | set(next_code_edit):
26 | code_edit = next_code_edit
27 | code_edit.gui_input.connect(_on_text_edit_gui_input)
28 | code_edit.text_changed.connect(_on_text_edit_text_changed)
29 | get:
30 | return code_edit
31 |
32 | var results: Array = []
33 | var result_index: int = -1:
34 | set(next_result_index):
35 | result_index = next_result_index
36 | if results.size() > 0:
37 | var r = results[result_index]
38 | code_edit.set_caret_line(r[0])
39 | code_edit.select(r[0], r[1], r[0], r[1] + r[2])
40 | else:
41 | result_index = -1
42 | if is_instance_valid(code_edit):
43 | code_edit.deselect()
44 |
45 | result_label.text = DialogueConstants.translate(&"n_of_n").format({ index = result_index + 1, total = results.size() })
46 | get:
47 | return result_index
48 |
49 |
50 | func _ready() -> void:
51 | apply_theme()
52 |
53 | input.placeholder_text = DialogueConstants.translate(&"search.placeholder")
54 | previous_button.tooltip_text = DialogueConstants.translate(&"search.previous")
55 | next_button.tooltip_text = DialogueConstants.translate(&"search.next")
56 | match_case_button.text = DialogueConstants.translate(&"search.match_case")
57 | $Search/ReplaceCheckButton.text = DialogueConstants.translate(&"search.toggle_replace")
58 | replace_button.text = DialogueConstants.translate(&"search.replace")
59 | replace_all_button.text = DialogueConstants.translate(&"search.replace_all")
60 | $Replace/ReplaceLabel.text = DialogueConstants.translate(&"search.replace_with")
61 |
62 | self.result_index = -1
63 |
64 | replace_panel.hide()
65 | replace_button.disabled = true
66 | replace_all_button.disabled = true
67 |
68 | hide()
69 |
70 |
71 | func focus_line_edit() -> void:
72 | input.grab_focus()
73 | input.select_all()
74 |
75 |
76 | func apply_theme() -> void:
77 | if is_instance_valid(previous_button):
78 | previous_button.icon = get_theme_icon("ArrowLeft", "EditorIcons")
79 | if is_instance_valid(next_button):
80 | next_button.icon = get_theme_icon("ArrowRight", "EditorIcons")
81 |
82 |
83 | # Find text in the code
84 | func search(text: String = "", default_result_index: int = 0) -> void:
85 | results.clear()
86 |
87 | if text == "":
88 | text = input.text
89 |
90 | var lines = code_edit.text.split("\n")
91 | for line_number in range(0, lines.size()):
92 | var line = lines[line_number]
93 |
94 | var column = find_in_line(line, text, 0)
95 | while column > -1:
96 | results.append([line_number, column, text.length()])
97 | column = find_in_line(line, text, column + 1)
98 |
99 | if results.size() > 0:
100 | replace_button.disabled = false
101 | replace_all_button.disabled = false
102 | else:
103 | replace_button.disabled = true
104 | replace_all_button.disabled = true
105 |
106 | self.result_index = clamp(default_result_index, 0, results.size() - 1)
107 |
108 |
109 | # Find text in a string and match case if requested
110 | func find_in_line(line: String, text: String, from_index: int = 0) -> int:
111 | if match_case_button.button_pressed:
112 | return line.find(text, from_index)
113 | else:
114 | return line.findn(text, from_index)
115 |
116 |
117 | #region Signals
118 |
119 |
120 | func _on_text_edit_gui_input(event: InputEvent) -> void:
121 | if event is InputEventKey and event.is_pressed():
122 | match event.as_text():
123 | "Ctrl+F", "Command+F":
124 | open_requested.emit()
125 | get_viewport().set_input_as_handled()
126 | "Ctrl+Shift+R", "Command+Shift+R":
127 | replace_check_button.set_pressed(true)
128 | open_requested.emit()
129 | get_viewport().set_input_as_handled()
130 |
131 |
132 | func _on_text_edit_text_changed() -> void:
133 | results.clear()
134 |
135 |
136 | func _on_search_and_replace_theme_changed() -> void:
137 | apply_theme()
138 |
139 |
140 | func _on_input_text_changed(new_text: String) -> void:
141 | search(new_text)
142 |
143 |
144 | func _on_previous_button_pressed() -> void:
145 | self.result_index = wrapi(result_index - 1, 0, results.size())
146 |
147 |
148 | func _on_next_button_pressed() -> void:
149 | self.result_index = wrapi(result_index + 1, 0, results.size())
150 |
151 |
152 | func _on_search_and_replace_visibility_changed() -> void:
153 | if is_instance_valid(input):
154 | if visible:
155 | input.grab_focus()
156 | var selection = code_edit.get_selected_text()
157 | if input.text == "" and selection != "":
158 | input.text = selection
159 | search(selection)
160 | else:
161 | search()
162 | else:
163 | input.text = ""
164 |
165 |
166 | func _on_input_gui_input(event: InputEvent) -> void:
167 | if event is InputEventKey and event.is_pressed():
168 | match event.as_text():
169 | "Enter":
170 | search(input.text)
171 | "Escape":
172 | emit_signal("close_requested")
173 |
174 |
175 | func _on_replace_button_pressed() -> void:
176 | if result_index == -1: return
177 |
178 | # Replace the selection at result index
179 | var r: Array = results[result_index]
180 | code_edit.begin_complex_operation()
181 | var lines: PackedStringArray = code_edit.text.split("\n")
182 | var line: String = lines[r[0]]
183 | line = line.substr(0, r[1]) + replace_input.text + line.substr(r[1] + r[2])
184 | lines[r[0]] = line
185 | code_edit.text = "\n".join(lines)
186 | code_edit.end_complex_operation()
187 | code_edit.text_changed.emit()
188 |
189 | search(input.text, result_index)
190 |
191 |
192 | func _on_replace_all_button_pressed() -> void:
193 | if match_case_button.button_pressed:
194 | code_edit.text = code_edit.text.replace(input.text, replace_input.text)
195 | else:
196 | code_edit.text = code_edit.text.replacen(input.text, replace_input.text)
197 | search()
198 | code_edit.text_changed.emit()
199 |
200 |
201 | func _on_replace_check_button_toggled(button_pressed: bool) -> void:
202 | replace_panel.visible = button_pressed
203 | if button_pressed:
204 | replace_input.grab_focus()
205 |
206 |
207 | func _on_input_focus_entered() -> void:
208 | if results.size() == 0:
209 | search()
210 | else:
211 | self.result_index = result_index
212 |
213 |
214 | func _on_match_case_check_box_toggled(button_pressed: bool) -> void:
215 | search()
216 |
217 |
218 | #endregion
219 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/search_and_replace.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cijsmjkq21cdq
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/search_and_replace.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=2 format=3 uid="uid://gr8nakpbrhby"]
2 |
3 | [ext_resource type="Script" uid="uid://cijsmjkq21cdq" path="res://addons/dialogue_manager/components/search_and_replace.gd" id="1_8oj1f"]
4 |
5 | [node name="SearchAndReplace" type="VBoxContainer"]
6 | visible = false
7 | anchors_preset = 10
8 | anchor_right = 1.0
9 | offset_bottom = 31.0
10 | grow_horizontal = 2
11 | size_flags_horizontal = 3
12 | script = ExtResource("1_8oj1f")
13 |
14 | [node name="Search" type="HBoxContainer" parent="."]
15 | layout_mode = 2
16 |
17 | [node name="Input" type="LineEdit" parent="Search"]
18 | layout_mode = 2
19 | size_flags_horizontal = 3
20 | placeholder_text = "Text to search for"
21 | metadata/_edit_use_custom_anchors = true
22 |
23 | [node name="MatchCaseCheckBox" type="CheckBox" parent="Search"]
24 | layout_mode = 2
25 | text = "Match case"
26 |
27 | [node name="VSeparator" type="VSeparator" parent="Search"]
28 | layout_mode = 2
29 |
30 | [node name="PreviousButton" type="Button" parent="Search"]
31 | layout_mode = 2
32 | tooltip_text = "Previous"
33 | flat = true
34 |
35 | [node name="ResultLabel" type="Label" parent="Search"]
36 | layout_mode = 2
37 | text = "0 of 0"
38 |
39 | [node name="NextButton" type="Button" parent="Search"]
40 | layout_mode = 2
41 | tooltip_text = "Next"
42 | flat = true
43 |
44 | [node name="VSeparator2" type="VSeparator" parent="Search"]
45 | layout_mode = 2
46 |
47 | [node name="ReplaceCheckButton" type="CheckButton" parent="Search"]
48 | layout_mode = 2
49 | text = "Replace"
50 |
51 | [node name="Replace" type="HBoxContainer" parent="."]
52 | visible = false
53 | layout_mode = 2
54 |
55 | [node name="ReplaceLabel" type="Label" parent="Replace"]
56 | layout_mode = 2
57 | text = "Replace with:"
58 |
59 | [node name="Input" type="LineEdit" parent="Replace"]
60 | layout_mode = 2
61 | size_flags_horizontal = 3
62 |
63 | [node name="ReplaceButton" type="Button" parent="Replace"]
64 | layout_mode = 2
65 | disabled = true
66 | text = "Replace"
67 | flat = true
68 |
69 | [node name="ReplaceAllButton" type="Button" parent="Replace"]
70 | layout_mode = 2
71 | disabled = true
72 | text = "Replace all"
73 | flat = true
74 |
75 | [connection signal="theme_changed" from="." to="." method="_on_search_and_replace_theme_changed"]
76 | [connection signal="visibility_changed" from="." to="." method="_on_search_and_replace_visibility_changed"]
77 | [connection signal="focus_entered" from="Search/Input" to="." method="_on_input_focus_entered"]
78 | [connection signal="gui_input" from="Search/Input" to="." method="_on_input_gui_input"]
79 | [connection signal="text_changed" from="Search/Input" to="." method="_on_input_text_changed"]
80 | [connection signal="toggled" from="Search/MatchCaseCheckBox" to="." method="_on_match_case_check_box_toggled"]
81 | [connection signal="pressed" from="Search/PreviousButton" to="." method="_on_previous_button_pressed"]
82 | [connection signal="pressed" from="Search/NextButton" to="." method="_on_next_button_pressed"]
83 | [connection signal="toggled" from="Search/ReplaceCheckButton" to="." method="_on_replace_check_button_toggled"]
84 | [connection signal="focus_entered" from="Replace/Input" to="." method="_on_input_focus_entered"]
85 | [connection signal="gui_input" from="Replace/Input" to="." method="_on_input_gui_input"]
86 | [connection signal="pressed" from="Replace/ReplaceButton" to="." method="_on_replace_button_pressed"]
87 | [connection signal="pressed" from="Replace/ReplaceAllButton" to="." method="_on_replace_all_button_pressed"]
88 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/title_list.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | extends VBoxContainer
3 |
4 | signal title_selected(title: String)
5 |
6 |
7 | const DialogueConstants = preload("../constants.gd")
8 |
9 |
10 | @onready var filter_edit: LineEdit = $FilterEdit
11 | @onready var list: ItemList = $List
12 |
13 | var titles: PackedStringArray:
14 | set(next_titles):
15 | titles = next_titles
16 | apply_filter()
17 | get:
18 | return titles
19 |
20 | var filter: String:
21 | set(next_filter):
22 | filter = next_filter
23 | apply_filter()
24 | get:
25 | return filter
26 |
27 |
28 | func _ready() -> void:
29 | apply_theme()
30 |
31 | filter_edit.placeholder_text = DialogueConstants.translate(&"titles_list.filter")
32 |
33 |
34 | func select_title(title: String) -> void:
35 | list.deselect_all()
36 | for i in range(0, list.get_item_count()):
37 | if list.get_item_text(i) == title.strip_edges():
38 | list.select(i)
39 |
40 |
41 | func apply_filter() -> void:
42 | list.clear()
43 | for title in titles:
44 | if filter == "" or filter.to_lower() in title.to_lower():
45 | list.add_item(title.strip_edges())
46 |
47 |
48 | func apply_theme() -> void:
49 | if is_instance_valid(filter_edit):
50 | filter_edit.right_icon = get_theme_icon("Search", "EditorIcons")
51 | if is_instance_valid(list):
52 | list.add_theme_stylebox_override("panel", get_theme_stylebox("panel", "Panel"))
53 |
54 |
55 | ### Signals
56 |
57 |
58 | func _on_theme_changed() -> void:
59 | apply_theme()
60 |
61 |
62 | func _on_filter_edit_text_changed(new_text: String) -> void:
63 | self.filter = new_text
64 |
65 |
66 | func _on_list_item_clicked(index: int, at_position: Vector2, mouse_button_index: int) -> void:
67 | if mouse_button_index == MOUSE_BUTTON_LEFT:
68 | var title = list.get_item_text(index)
69 | title_selected.emit(title)
70 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/title_list.gd.uid:
--------------------------------------------------------------------------------
1 | uid://d0k2wndjj0ifm
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/title_list.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=2 format=3 uid="uid://ctns6ouwwd68i"]
2 |
3 | [ext_resource type="Script" uid="uid://d0k2wndjj0ifm" path="res://addons/dialogue_manager/components/title_list.gd" id="1_5qqmd"]
4 |
5 | [node name="TitleList" type="VBoxContainer"]
6 | anchors_preset = 15
7 | anchor_right = 1.0
8 | anchor_bottom = 1.0
9 | grow_horizontal = 2
10 | grow_vertical = 2
11 | size_flags_horizontal = 3
12 | size_flags_vertical = 3
13 | script = ExtResource("1_5qqmd")
14 |
15 | [node name="FilterEdit" type="LineEdit" parent="."]
16 | layout_mode = 2
17 | placeholder_text = "Filter titles"
18 | clear_button_enabled = true
19 |
20 | [node name="List" type="ItemList" parent="."]
21 | layout_mode = 2
22 | size_flags_vertical = 3
23 | allow_reselect = true
24 |
25 | [connection signal="theme_changed" from="." to="." method="_on_theme_changed"]
26 | [connection signal="text_changed" from="FilterEdit" to="." method="_on_filter_edit_text_changed"]
27 | [connection signal="item_clicked" from="List" to="." method="_on_list_item_clicked"]
28 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/update_button.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | extends Button
3 |
4 | const DialogueConstants = preload("../constants.gd")
5 | const DialogueSettings = preload("../settings.gd")
6 |
7 | const REMOTE_RELEASES_URL = "https://api.github.com/repos/nathanhoad/godot_dialogue_manager/releases"
8 |
9 |
10 | @onready var http_request: HTTPRequest = $HTTPRequest
11 | @onready var download_dialog: AcceptDialog = $DownloadDialog
12 | @onready var download_update_panel = $DownloadDialog/DownloadUpdatePanel
13 | @onready var needs_reload_dialog: AcceptDialog = $NeedsReloadDialog
14 | @onready var update_failed_dialog: AcceptDialog = $UpdateFailedDialog
15 | @onready var timer: Timer = $Timer
16 |
17 | var needs_reload: bool = false
18 |
19 | # A lambda that gets called just before refreshing the plugin. Return false to stop the reload.
20 | var on_before_refresh: Callable = func(): return true
21 |
22 |
23 | func _ready() -> void:
24 | hide()
25 | apply_theme()
26 |
27 | # Check for updates on GitHub
28 | check_for_update()
29 |
30 | # Check again every few hours
31 | timer.start(60 * 60 * 12)
32 |
33 |
34 | # Convert a version number to an actually comparable number
35 | func version_to_number(version: String) -> int:
36 | var bits = version.split(".")
37 | return bits[0].to_int() * 1000000 + bits[1].to_int() * 1000 + bits[2].to_int()
38 |
39 |
40 | func apply_theme() -> void:
41 | var color: Color = get_theme_color("success_color", "Editor")
42 |
43 | if needs_reload:
44 | color = get_theme_color("error_color", "Editor")
45 | icon = get_theme_icon("Reload", "EditorIcons")
46 | add_theme_color_override("icon_normal_color", color)
47 | add_theme_color_override("icon_focus_color", color)
48 | add_theme_color_override("icon_hover_color", color)
49 |
50 | add_theme_color_override("font_color", color)
51 | add_theme_color_override("font_focus_color", color)
52 | add_theme_color_override("font_hover_color", color)
53 |
54 |
55 | func check_for_update() -> void:
56 | if DialogueSettings.get_user_value("check_for_updates", true):
57 | http_request.request(REMOTE_RELEASES_URL)
58 |
59 |
60 | ### Signals
61 |
62 |
63 | func _on_http_request_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray) -> void:
64 | if result != HTTPRequest.RESULT_SUCCESS: return
65 |
66 | var current_version: String = Engine.get_meta("DialogueManagerPlugin").get_version()
67 |
68 | # Work out the next version from the releases information on GitHub
69 | var response = JSON.parse_string(body.get_string_from_utf8())
70 | if typeof(response) != TYPE_ARRAY: return
71 |
72 | # GitHub releases are in order of creation, not order of version
73 | var versions = (response as Array).filter(func(release):
74 | var version: String = release.tag_name.substr(1)
75 | var major_version: int = version.split(".")[0].to_int()
76 | var current_major_version: int = current_version.split(".")[0].to_int()
77 | return major_version == current_major_version and version_to_number(version) > version_to_number(current_version)
78 | )
79 | if versions.size() > 0:
80 | download_update_panel.next_version_release = versions[0]
81 | text = DialogueConstants.translate(&"update.available").format({ version = versions[0].tag_name.substr(1) })
82 | show()
83 |
84 |
85 | func _on_update_button_pressed() -> void:
86 | if needs_reload:
87 | var will_refresh = on_before_refresh.call()
88 | if will_refresh:
89 | EditorInterface.restart_editor(true)
90 | else:
91 | var scale: float = EditorInterface.get_editor_scale()
92 | download_dialog.min_size = Vector2(300, 250) * scale
93 | download_dialog.popup_centered()
94 |
95 |
96 | func _on_download_dialog_close_requested() -> void:
97 | download_dialog.hide()
98 |
99 |
100 | func _on_download_update_panel_updated(updated_to_version: String) -> void:
101 | download_dialog.hide()
102 |
103 | needs_reload_dialog.dialog_text = DialogueConstants.translate(&"update.needs_reload")
104 | needs_reload_dialog.ok_button_text = DialogueConstants.translate(&"update.reload_ok_button")
105 | needs_reload_dialog.cancel_button_text = DialogueConstants.translate(&"update.reload_cancel_button")
106 | needs_reload_dialog.popup_centered()
107 |
108 | needs_reload = true
109 | text = DialogueConstants.translate(&"update.reload_project")
110 | apply_theme()
111 |
112 |
113 | func _on_download_update_panel_failed() -> void:
114 | download_dialog.hide()
115 | update_failed_dialog.dialog_text = DialogueConstants.translate(&"update.failed")
116 | update_failed_dialog.popup_centered()
117 |
118 |
119 | func _on_needs_reload_dialog_confirmed() -> void:
120 | EditorInterface.restart_editor(true)
121 |
122 |
123 | func _on_timer_timeout() -> void:
124 | if not needs_reload:
125 | check_for_update()
126 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/update_button.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cr1tt12dh5ecr
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/components/update_button.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=3 format=3 uid="uid://co8yl23idiwbi"]
2 |
3 | [ext_resource type="Script" uid="uid://cr1tt12dh5ecr" path="res://addons/dialogue_manager/components/update_button.gd" id="1_d2tpb"]
4 | [ext_resource type="PackedScene" uid="uid://qdxrxv3c3hxk" path="res://addons/dialogue_manager/components/download_update_panel.tscn" id="2_iwm7r"]
5 |
6 | [node name="UpdateButton" type="Button"]
7 | visible = false
8 | offset_right = 8.0
9 | offset_bottom = 8.0
10 | theme_override_colors/font_color = Color(0, 0, 0, 1)
11 | theme_override_colors/font_hover_color = Color(0, 0, 0, 1)
12 | theme_override_colors/font_focus_color = Color(0, 0, 0, 1)
13 | text = "v2.9.0 available"
14 | flat = true
15 | script = ExtResource("1_d2tpb")
16 |
17 | [node name="HTTPRequest" type="HTTPRequest" parent="."]
18 |
19 | [node name="DownloadDialog" type="AcceptDialog" parent="."]
20 | title = "Download update"
21 | size = Vector2i(400, 300)
22 | unresizable = true
23 | min_size = Vector2i(300, 250)
24 | ok_button_text = "Close"
25 |
26 | [node name="DownloadUpdatePanel" parent="DownloadDialog" instance=ExtResource("2_iwm7r")]
27 |
28 | [node name="UpdateFailedDialog" type="AcceptDialog" parent="."]
29 | dialog_text = "You have been updated to version 2.4.3"
30 |
31 | [node name="NeedsReloadDialog" type="ConfirmationDialog" parent="."]
32 |
33 | [node name="Timer" type="Timer" parent="."]
34 | wait_time = 14400.0
35 |
36 | [connection signal="pressed" from="." to="." method="_on_update_button_pressed"]
37 | [connection signal="request_completed" from="HTTPRequest" to="." method="_on_http_request_request_completed"]
38 | [connection signal="close_requested" from="DownloadDialog" to="." method="_on_download_dialog_close_requested"]
39 | [connection signal="failed" from="DownloadDialog/DownloadUpdatePanel" to="." method="_on_download_update_panel_failed"]
40 | [connection signal="updated" from="DownloadDialog/DownloadUpdatePanel" to="." method="_on_download_update_panel_updated"]
41 | [connection signal="confirmed" from="NeedsReloadDialog" to="." method="_on_needs_reload_dialog_confirmed"]
42 | [connection signal="timeout" from="Timer" to="." method="_on_timer_timeout"]
43 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/constants.gd:
--------------------------------------------------------------------------------
1 | class_name DMConstants extends RefCounted
2 |
3 |
4 | const USER_CONFIG_PATH = "user://dialogue_manager_user_config.json"
5 | const CACHE_PATH = "user://dialogue_manager_cache.json"
6 |
7 |
8 | enum MutationBehaviour {
9 | Wait,
10 | DoNotWait,
11 | Skip
12 | }
13 |
14 | enum TranslationSource {
15 | None,
16 | Guess,
17 | CSV,
18 | PO
19 | }
20 |
21 | # Token types
22 |
23 | const TOKEN_FUNCTION = &"function"
24 | const TOKEN_DICTIONARY_REFERENCE = &"dictionary_reference"
25 | const TOKEN_DICTIONARY_NESTED_REFERENCE = &"dictionary_nested_reference"
26 | const TOKEN_GROUP = &"group"
27 | const TOKEN_ARRAY = &"array"
28 | const TOKEN_DICTIONARY = &"dictionary"
29 | const TOKEN_PARENS_OPEN = &"parens_open"
30 | const TOKEN_PARENS_CLOSE = &"parens_close"
31 | const TOKEN_BRACKET_OPEN = &"bracket_open"
32 | const TOKEN_BRACKET_CLOSE = &"bracket_close"
33 | const TOKEN_BRACE_OPEN = &"brace_open"
34 | const TOKEN_BRACE_CLOSE = &"brace_close"
35 | const TOKEN_COLON = &"colon"
36 | const TOKEN_COMPARISON = &"comparison"
37 | const TOKEN_ASSIGNMENT = &"assignment"
38 | const TOKEN_OPERATOR = &"operator"
39 | const TOKEN_COMMA = &"comma"
40 | const TOKEN_NULL_COALESCE = &"null_coalesce"
41 | const TOKEN_DOT = &"dot"
42 | const TOKEN_CONDITION = &"condition"
43 | const TOKEN_BOOL = &"bool"
44 | const TOKEN_NOT = &"not"
45 | const TOKEN_AND_OR = &"and_or"
46 | const TOKEN_STRING = &"string"
47 | const TOKEN_NUMBER = &"number"
48 | const TOKEN_VARIABLE = &"variable"
49 | const TOKEN_COMMENT = &"comment"
50 |
51 | const TOKEN_VALUE = &"value"
52 | const TOKEN_ERROR = &"error"
53 |
54 | # Line types
55 |
56 | const TYPE_UNKNOWN = &""
57 | const TYPE_IMPORT = &"import"
58 | const TYPE_USING = &"using"
59 | const TYPE_COMMENT = &"comment"
60 | const TYPE_RESPONSE = &"response"
61 | const TYPE_TITLE = &"title"
62 | const TYPE_CONDITION = &"condition"
63 | const TYPE_WHILE = &"while"
64 | const TYPE_MATCH = &"match"
65 | const TYPE_WHEN = &"when"
66 | const TYPE_MUTATION = &"mutation"
67 | const TYPE_GOTO = &"goto"
68 | const TYPE_DIALOGUE = &"dialogue"
69 | const TYPE_RANDOM = &"random"
70 | const TYPE_ERROR = &"error"
71 |
72 | # Line IDs
73 |
74 | const ID_NULL = &""
75 | const ID_ERROR = &"error"
76 | const ID_ERROR_INVALID_TITLE = &"invalid title"
77 | const ID_ERROR_TITLE_HAS_NO_BODY = &"title has no body"
78 | const ID_END = &"end"
79 | const ID_END_CONVERSATION = &"end!"
80 |
81 | # Errors
82 |
83 | const ERR_ERRORS_IN_IMPORTED_FILE = 100
84 | const ERR_FILE_ALREADY_IMPORTED = 101
85 | const ERR_DUPLICATE_IMPORT_NAME = 102
86 | const ERR_EMPTY_TITLE = 103
87 | const ERR_DUPLICATE_TITLE = 104
88 | const ERR_TITLE_INVALID_CHARACTERS = 106
89 | const ERR_UNKNOWN_TITLE = 107
90 | const ERR_INVALID_TITLE_REFERENCE = 108
91 | const ERR_TITLE_REFERENCE_HAS_NO_CONTENT = 109
92 | const ERR_INVALID_EXPRESSION = 110
93 | const ERR_UNEXPECTED_CONDITION = 111
94 | const ERR_DUPLICATE_ID = 112
95 | const ERR_MISSING_ID = 113
96 | const ERR_INVALID_INDENTATION = 114
97 | const ERR_INVALID_CONDITION_INDENTATION = 115
98 | const ERR_INCOMPLETE_EXPRESSION = 116
99 | const ERR_INVALID_EXPRESSION_FOR_VALUE = 117
100 | const ERR_UNKNOWN_LINE_SYNTAX = 118
101 | const ERR_TITLE_BEGINS_WITH_NUMBER = 119
102 | const ERR_UNEXPECTED_END_OF_EXPRESSION = 120
103 | const ERR_UNEXPECTED_FUNCTION = 121
104 | const ERR_UNEXPECTED_BRACKET = 122
105 | const ERR_UNEXPECTED_CLOSING_BRACKET = 123
106 | const ERR_MISSING_CLOSING_BRACKET = 124
107 | const ERR_UNEXPECTED_OPERATOR = 125
108 | const ERR_UNEXPECTED_COMMA = 126
109 | const ERR_UNEXPECTED_COLON = 127
110 | const ERR_UNEXPECTED_DOT = 128
111 | const ERR_UNEXPECTED_BOOLEAN = 129
112 | const ERR_UNEXPECTED_STRING = 130
113 | const ERR_UNEXPECTED_NUMBER = 131
114 | const ERR_UNEXPECTED_VARIABLE = 132
115 | const ERR_INVALID_INDEX = 133
116 | const ERR_UNEXPECTED_ASSIGNMENT = 134
117 | const ERR_UNKNOWN_USING = 135
118 | const ERR_EXPECTED_WHEN_OR_ELSE = 136
119 | const ERR_ONLY_ONE_ELSE_ALLOWED = 137
120 | const ERR_WHEN_MUST_BELONG_TO_MATCH = 138
121 | const ERR_CONCURRENT_LINE_WITHOUT_ORIGIN = 139
122 | const ERR_GOTO_NOT_ALLOWED_ON_CONCURRECT_LINES = 140
123 | const ERR_UNEXPECTED_SYNTAX_ON_NESTED_DIALOGUE_LINE = 141
124 | const ERR_NESTED_DIALOGUE_INVALID_JUMP = 142
125 |
126 |
127 | static var _current_locale: String = ""
128 | static var _current_translation: Translation
129 |
130 |
131 | ## Get the error message
132 | static func get_error_message(error: int) -> String:
133 | match error:
134 | ERR_ERRORS_IN_IMPORTED_FILE:
135 | return translate(&"errors.import_errors")
136 | ERR_FILE_ALREADY_IMPORTED:
137 | return translate(&"errors.already_imported")
138 | ERR_DUPLICATE_IMPORT_NAME:
139 | return translate(&"errors.duplicate_import")
140 | ERR_EMPTY_TITLE:
141 | return translate(&"errors.empty_title")
142 | ERR_DUPLICATE_TITLE:
143 | return translate(&"errors.duplicate_title")
144 | ERR_TITLE_INVALID_CHARACTERS:
145 | return translate(&"errors.invalid_title_string")
146 | ERR_TITLE_BEGINS_WITH_NUMBER:
147 | return translate(&"errors.invalid_title_number")
148 | ERR_UNKNOWN_TITLE:
149 | return translate(&"errors.unknown_title")
150 | ERR_INVALID_TITLE_REFERENCE:
151 | return translate(&"errors.jump_to_invalid_title")
152 | ERR_TITLE_REFERENCE_HAS_NO_CONTENT:
153 | return translate(&"errors.title_has_no_content")
154 | ERR_INVALID_EXPRESSION:
155 | return translate(&"errors.invalid_expression")
156 | ERR_UNEXPECTED_CONDITION:
157 | return translate(&"errors.unexpected_condition")
158 | ERR_DUPLICATE_ID:
159 | return translate(&"errors.duplicate_id")
160 | ERR_MISSING_ID:
161 | return translate(&"errors.missing_id")
162 | ERR_INVALID_INDENTATION:
163 | return translate(&"errors.invalid_indentation")
164 | ERR_INVALID_CONDITION_INDENTATION:
165 | return translate(&"errors.condition_has_no_content")
166 | ERR_INCOMPLETE_EXPRESSION:
167 | return translate(&"errors.incomplete_expression")
168 | ERR_INVALID_EXPRESSION_FOR_VALUE:
169 | return translate(&"errors.invalid_expression_for_value")
170 | ERR_FILE_NOT_FOUND:
171 | return translate(&"errors.file_not_found")
172 | ERR_UNEXPECTED_END_OF_EXPRESSION:
173 | return translate(&"errors.unexpected_end_of_expression")
174 | ERR_UNEXPECTED_FUNCTION:
175 | return translate(&"errors.unexpected_function")
176 | ERR_UNEXPECTED_BRACKET:
177 | return translate(&"errors.unexpected_bracket")
178 | ERR_UNEXPECTED_CLOSING_BRACKET:
179 | return translate(&"errors.unexpected_closing_bracket")
180 | ERR_MISSING_CLOSING_BRACKET:
181 | return translate(&"errors.missing_closing_bracket")
182 | ERR_UNEXPECTED_OPERATOR:
183 | return translate(&"errors.unexpected_operator")
184 | ERR_UNEXPECTED_COMMA:
185 | return translate(&"errors.unexpected_comma")
186 | ERR_UNEXPECTED_COLON:
187 | return translate(&"errors.unexpected_colon")
188 | ERR_UNEXPECTED_DOT:
189 | return translate(&"errors.unexpected_dot")
190 | ERR_UNEXPECTED_BOOLEAN:
191 | return translate(&"errors.unexpected_boolean")
192 | ERR_UNEXPECTED_STRING:
193 | return translate(&"errors.unexpected_string")
194 | ERR_UNEXPECTED_NUMBER:
195 | return translate(&"errors.unexpected_number")
196 | ERR_UNEXPECTED_VARIABLE:
197 | return translate(&"errors.unexpected_variable")
198 | ERR_INVALID_INDEX:
199 | return translate(&"errors.invalid_index")
200 | ERR_UNEXPECTED_ASSIGNMENT:
201 | return translate(&"errors.unexpected_assignment")
202 | ERR_UNKNOWN_USING:
203 | return translate(&"errors.unknown_using")
204 | ERR_EXPECTED_WHEN_OR_ELSE:
205 | return translate(&"errors.expected_when_or_else")
206 | ERR_ONLY_ONE_ELSE_ALLOWED:
207 | return translate(&"errors.only_one_else_allowed")
208 | ERR_WHEN_MUST_BELONG_TO_MATCH:
209 | return translate(&"errors.when_must_belong_to_match")
210 | ERR_CONCURRENT_LINE_WITHOUT_ORIGIN:
211 | return translate(&"errors.concurrent_line_without_origin")
212 | ERR_GOTO_NOT_ALLOWED_ON_CONCURRECT_LINES:
213 | return translate(&"errors.goto_not_allowed_on_concurrect_lines")
214 | ERR_UNEXPECTED_SYNTAX_ON_NESTED_DIALOGUE_LINE:
215 | return translate(&"errors.unexpected_syntax_on_nested_dialogue_line")
216 | ERR_NESTED_DIALOGUE_INVALID_JUMP:
217 | return translate(&"errors.err_nested_dialogue_invalid_jump")
218 |
219 | return translate(&"errors.unknown")
220 |
221 |
222 | static func translate(string: String) -> String:
223 | var locale: String = TranslationServer.get_tool_locale()
224 | if _current_translation == null or _current_locale != locale:
225 | var base_path: String = new().get_script().resource_path.get_base_dir()
226 | var translation_path: String = "%s/l10n/%s.po" % [base_path, locale]
227 | var fallback_translation_path: String = "%s/l10n/%s.po" % [base_path, locale.substr(0, 2)]
228 | var en_translation_path: String = "%s/l10n/en.po" % base_path
229 | _current_translation = load(translation_path if FileAccess.file_exists(translation_path) else (fallback_translation_path if FileAccess.file_exists(fallback_translation_path) else en_translation_path))
230 | _current_locale = locale
231 | return _current_translation.get_message(string)
232 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/constants.gd.uid:
--------------------------------------------------------------------------------
1 | uid://b1oarbmjtyesf
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/dialogue_label.gd:
--------------------------------------------------------------------------------
1 | @icon("./assets/icon.svg")
2 |
3 | @tool
4 |
5 | ## A RichTextLabel specifically for use with [b]Dialogue Manager[/b] dialogue.
6 | class_name DialogueLabel extends RichTextLabel
7 |
8 |
9 | ## Emitted for each letter typed out.
10 | signal spoke(letter: String, letter_index: int, speed: float)
11 |
12 | ## Emitted when typing paused for a `[wait]`
13 | signal paused_typing(duration: float)
14 |
15 | ## Emitted when the player skips the typing of dialogue.
16 | signal skipped_typing()
17 |
18 | ## Emitted when typing finishes.
19 | signal finished_typing()
20 |
21 |
22 | # The action to press to skip typing.
23 | @export var skip_action: StringName = &"ui_cancel"
24 |
25 | ## The speed with which the text types out.
26 | @export var seconds_per_step: float = 0.02
27 |
28 | ## Automatically have a brief pause when these characters are encountered.
29 | @export var pause_at_characters: String = ".?!"
30 |
31 | ## Don't auto pause if the character after the pause is one of these.
32 | @export var skip_pause_at_character_if_followed_by: String = ")\""
33 |
34 | ## Don't auto pause after these abbreviations (only if "." is in `pause_at_characters`).[br]
35 | ## Abbreviations are limitted to 5 characters in length [br]
36 | ## Does not support multi-period abbreviations (ex. "p.m.")
37 | @export var skip_pause_at_abbreviations: PackedStringArray = ["Mr", "Mrs", "Ms", "Dr", "etc", "eg", "ex"]
38 |
39 | ## The amount of time to pause when exposing a character present in `pause_at_characters`.
40 | @export var seconds_per_pause_step: float = 0.3
41 |
42 | var _already_mutated_indices: PackedInt32Array = []
43 |
44 |
45 | ## The current line of dialogue.
46 | var dialogue_line:
47 | set(next_dialogue_line):
48 | dialogue_line = next_dialogue_line
49 | custom_minimum_size = Vector2.ZERO
50 | text = ""
51 | text = dialogue_line.text
52 | get:
53 | return dialogue_line
54 |
55 | ## Whether the label is currently typing itself out.
56 | var is_typing: bool = false:
57 | set(value):
58 | var is_finished: bool = is_typing != value and value == false
59 | is_typing = value
60 | if is_finished:
61 | finished_typing.emit()
62 | get:
63 | return is_typing
64 |
65 | var _last_wait_index: int = -1
66 | var _last_mutation_index: int = -1
67 | var _waiting_seconds: float = 0
68 | var _is_awaiting_mutation: bool = false
69 |
70 |
71 | func _process(delta: float) -> void:
72 | if self.is_typing:
73 | # Type out text
74 | if visible_ratio < 1:
75 | # See if we are waiting
76 | if _waiting_seconds > 0:
77 | _waiting_seconds = _waiting_seconds - delta
78 | # If we are no longer waiting then keep typing
79 | if _waiting_seconds <= 0:
80 | _type_next(delta, _waiting_seconds)
81 | else:
82 | # Make sure any mutations at the end of the line get run
83 | _mutate_inline_mutations(get_total_character_count())
84 | self.is_typing = false
85 |
86 |
87 | func _unhandled_input(event: InputEvent) -> void:
88 | # Note: this will no longer be reached if using Dialogue Manager > 2.32.2. To make skip handling
89 | # simpler (so all of mouse/keyboard/joypad are together) it is now the responsibility of the
90 | # dialogue balloon.
91 | if self.is_typing and visible_ratio < 1 and InputMap.has_action(skip_action) and event.is_action_pressed(skip_action):
92 | get_viewport().set_input_as_handled()
93 | skip_typing()
94 |
95 |
96 | ## Start typing out the text
97 | func type_out() -> void:
98 | text = dialogue_line.text
99 | visible_characters = 0
100 | visible_ratio = 0
101 | _waiting_seconds = 0
102 | _last_wait_index = -1
103 | _last_mutation_index = -1
104 | _already_mutated_indices.clear()
105 |
106 | self.is_typing = true
107 |
108 | # Allow typing listeners a chance to connect
109 | await get_tree().process_frame
110 |
111 | if get_total_character_count() == 0:
112 | self.is_typing = false
113 | elif seconds_per_step == 0:
114 | _mutate_remaining_mutations()
115 | visible_characters = get_total_character_count()
116 | self.is_typing = false
117 |
118 |
119 | ## Stop typing out the text and jump right to the end
120 | func skip_typing() -> void:
121 | _mutate_remaining_mutations()
122 | visible_characters = get_total_character_count()
123 | self.is_typing = false
124 | skipped_typing.emit()
125 |
126 |
127 | # Type out the next character(s)
128 | func _type_next(delta: float, seconds_needed: float) -> void:
129 | if _is_awaiting_mutation: return
130 |
131 | if visible_characters == get_total_character_count():
132 | return
133 |
134 | if _last_mutation_index != visible_characters:
135 | _last_mutation_index = visible_characters
136 | _mutate_inline_mutations(visible_characters)
137 | if _is_awaiting_mutation: return
138 |
139 | var additional_waiting_seconds: float = _get_pause(visible_characters)
140 |
141 | # Pause on characters like "."
142 | if _should_auto_pause():
143 | additional_waiting_seconds += seconds_per_pause_step
144 |
145 | # Pause at literal [wait] directives
146 | if _last_wait_index != visible_characters and additional_waiting_seconds > 0:
147 | _last_wait_index = visible_characters
148 | _waiting_seconds += additional_waiting_seconds
149 | paused_typing.emit(_get_pause(visible_characters))
150 | else:
151 | visible_characters += 1
152 | if visible_characters <= get_total_character_count():
153 | spoke.emit(get_parsed_text()[visible_characters - 1], visible_characters - 1, _get_speed(visible_characters))
154 | # See if there's time to type out some more in this frame
155 | seconds_needed += seconds_per_step * (1.0 / _get_speed(visible_characters))
156 | if seconds_needed > delta:
157 | _waiting_seconds += seconds_needed
158 | else:
159 | _type_next(delta, seconds_needed)
160 |
161 |
162 | # Get the pause for the current typing position if there is one
163 | func _get_pause(at_index: int) -> float:
164 | return dialogue_line.pauses.get(at_index, 0)
165 |
166 |
167 | # Get the speed for the current typing position
168 | func _get_speed(at_index: int) -> float:
169 | var speed: float = 1
170 | for index in dialogue_line.speeds:
171 | if index > at_index:
172 | return speed
173 | speed = dialogue_line.speeds[index]
174 | return speed
175 |
176 |
177 | # Run any inline mutations that haven't been run yet
178 | func _mutate_remaining_mutations() -> void:
179 | for i in range(visible_characters, get_total_character_count() + 1):
180 | _mutate_inline_mutations(i)
181 |
182 |
183 | # Run any mutations at the current typing position
184 | func _mutate_inline_mutations(index: int) -> void:
185 | for inline_mutation in dialogue_line.inline_mutations:
186 | # inline mutations are an array of arrays in the form of [character index, resolvable function]
187 | if inline_mutation[0] > index:
188 | return
189 | if inline_mutation[0] == index and not _already_mutated_indices.has(index):
190 | _is_awaiting_mutation = true
191 | # The DialogueManager can't be referenced directly here so we need to get it by its path
192 | await Engine.get_singleton("DialogueManager")._mutate(inline_mutation[1], dialogue_line.extra_game_states, true)
193 | _is_awaiting_mutation = false
194 |
195 | _already_mutated_indices.append(index)
196 |
197 |
198 | # Determine if the current autopause character at the cursor should qualify to pause typing.
199 | func _should_auto_pause() -> bool:
200 | if visible_characters == 0: return false
201 |
202 | var parsed_text: String = get_parsed_text()
203 |
204 | # Avoid outofbounds when the label auto-translates and the text changes to one shorter while typing out
205 | # Note: visible characters can be larger than parsed_text after a translation event
206 | if visible_characters >= parsed_text.length(): return false
207 |
208 | # Ignore pause characters if they are next to a non-pause character
209 | if parsed_text[visible_characters] in skip_pause_at_character_if_followed_by.split():
210 | return false
211 |
212 | # Ignore "." if it's between two numbers
213 | if visible_characters > 3 and parsed_text[visible_characters - 1] == ".":
214 | var possible_number: String = parsed_text.substr(visible_characters - 2, 3)
215 | if str(float(possible_number)).pad_decimals(1) == possible_number:
216 | return false
217 |
218 | # Ignore "." if it's used in an abbreviation
219 | # Note: does NOT support multi-period abbreviations (ex. p.m.)
220 | if "." in pause_at_characters and parsed_text[visible_characters - 1] == ".":
221 | for abbreviation in skip_pause_at_abbreviations:
222 | if visible_characters >= abbreviation.length():
223 | var previous_characters: String = parsed_text.substr(visible_characters - abbreviation.length() - 1, abbreviation.length())
224 | if previous_characters == abbreviation:
225 | return false
226 |
227 | # Ignore two non-"." characters next to each other
228 | var other_pause_characters: PackedStringArray = pause_at_characters.replace(".", "").split()
229 | if visible_characters > 1 and parsed_text[visible_characters - 1] in other_pause_characters and parsed_text[visible_characters] in other_pause_characters:
230 | return false
231 |
232 | return parsed_text[visible_characters - 1] in pause_at_characters.split()
233 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/dialogue_label.gd.uid:
--------------------------------------------------------------------------------
1 | uid://g32um0mltv5d
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/dialogue_label.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=2 format=3 uid="uid://ckvgyvclnwggo"]
2 |
3 | [ext_resource type="Script" uid="uid://g32um0mltv5d" path="res://addons/dialogue_manager/dialogue_label.gd" id="1_cital"]
4 |
5 | [node name="DialogueLabel" type="RichTextLabel"]
6 | anchors_preset = 10
7 | anchor_right = 1.0
8 | grow_horizontal = 2
9 | mouse_filter = 1
10 | bbcode_enabled = true
11 | fit_content = true
12 | scroll_active = false
13 | shortcut_keys_enabled = false
14 | meta_underlined = false
15 | hint_underlined = false
16 | deselect_on_focus_loss_enabled = false
17 | visible_characters_behavior = 1
18 | script = ExtResource("1_cital")
19 | skip_pause_at_abbreviations = PackedStringArray("Mr", "Mrs", "Ms", "Dr", "etc", "eg", "ex")
20 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/dialogue_line.gd:
--------------------------------------------------------------------------------
1 | ## A line of dialogue returned from [code]DialogueManager[/code].
2 | class_name DialogueLine extends RefCounted
3 |
4 |
5 | ## The ID of this line
6 | var id: String
7 |
8 | ## The internal type of this dialogue object. One of [code]TYPE_DIALOGUE[/code] or [code]TYPE_MUTATION[/code]
9 | var type: String = DMConstants.TYPE_DIALOGUE
10 |
11 | ## The next line ID after this line.
12 | var next_id: String = ""
13 |
14 | ## The character name that is saying this line.
15 | var character: String = ""
16 |
17 | ## A dictionary of variable replacements fo the character name. Generally for internal use only.
18 | var character_replacements: Array[Dictionary] = []
19 |
20 | ## The dialogue being spoken.
21 | var text: String = ""
22 |
23 | ## A dictionary of replacements for the text. Generally for internal use only.
24 | var text_replacements: Array[Dictionary] = []
25 |
26 | ## The key to use for translating this line.
27 | var translation_key: String = ""
28 |
29 | ## A map for when and for how long to pause while typing out the dialogue text.
30 | var pauses: Dictionary = {}
31 |
32 | ## A map for speed changes when typing out the dialogue text.
33 | var speeds: Dictionary = {}
34 |
35 | ## A map of any mutations to run while typing out the dialogue text.
36 | var inline_mutations: Array[Array] = []
37 |
38 | ## A list of responses attached to this line of dialogue.
39 | var responses: Array = []
40 |
41 | ## A list of lines that are spoken simultaneously with this one.
42 | var concurrent_lines: Array[DialogueLine] = []
43 |
44 | ## A list of any extra game states to check when resolving variables and mutations.
45 | var extra_game_states: Array = []
46 |
47 | ## How long to show this line before advancing to the next. Either a float (of seconds), [code]"auto"[/code], or [code]null[/code].
48 | var time: String = ""
49 |
50 | ## Any #tags that were included in the line
51 | var tags: PackedStringArray = []
52 |
53 | ## The mutation details if this is a mutation line (where [code]type == TYPE_MUTATION[/code]).
54 | var mutation: Dictionary = {}
55 |
56 | ## The conditions to check before including this line in the flow of dialogue. If failed the line will be skipped over.
57 | var conditions: Dictionary = {}
58 |
59 |
60 | func _init(data: Dictionary = {}) -> void:
61 | if data.size() > 0:
62 | id = data.id
63 | next_id = data.next_id
64 | type = data.type
65 | extra_game_states = data.get("extra_game_states", [])
66 |
67 | match type:
68 | DMConstants.TYPE_DIALOGUE:
69 | character = data.character
70 | character_replacements = data.get("character_replacements", [] as Array[Dictionary])
71 | text = data.text
72 | text_replacements = data.get("text_replacements", [] as Array[Dictionary])
73 | translation_key = data.get("translation_key", data.text)
74 | pauses = data.get("pauses", {})
75 | speeds = data.get("speeds", {})
76 | inline_mutations = data.get("inline_mutations", [] as Array[Array])
77 | time = data.get("time", "")
78 | tags = data.get("tags", [])
79 | concurrent_lines = data.get("concurrent_lines", [] as Array[DialogueLine])
80 |
81 | DMConstants.TYPE_MUTATION:
82 | mutation = data.mutation
83 |
84 |
85 | func _to_string() -> String:
86 | match type:
87 | DMConstants.TYPE_DIALOGUE:
88 | return "" % [character, text]
89 | DMConstants.TYPE_MUTATION:
90 | return ""
91 | return ""
92 |
93 |
94 | func get_tag_value(tag_name: String) -> String:
95 | var wrapped := "%s=" % tag_name
96 | for t in tags:
97 | if t.begins_with(wrapped):
98 | return t.replace(wrapped, "").strip_edges()
99 | return ""
100 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/dialogue_line.gd.uid:
--------------------------------------------------------------------------------
1 | uid://rhuq0eyf8ar2
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/dialogue_manager.gd.uid:
--------------------------------------------------------------------------------
1 | uid://c3rodes2l3gxb
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/dialogue_resource.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | @icon("./assets/icon.svg")
3 |
4 | ## A collection of dialogue lines for use with [code]DialogueManager[/code].
5 | class_name DialogueResource extends Resource
6 |
7 |
8 | const DialogueLine = preload("./dialogue_line.gd")
9 |
10 | ## A list of state shortcuts
11 | @export var using_states: PackedStringArray = []
12 |
13 | ## A map of titles and the lines they point to.
14 | @export var titles: Dictionary = {}
15 |
16 | ## A list of character names.
17 | @export var character_names: PackedStringArray = []
18 |
19 | ## The first title in the file.
20 | @export var first_title: String = ""
21 |
22 | ## A map of the encoded lines of dialogue.
23 | @export var lines: Dictionary = {}
24 |
25 | ## raw version of the text
26 | @export var raw_text: String
27 |
28 |
29 | ## Get the next printable line of dialogue, starting from a referenced line ([code]title[/code] can
30 | ## be a title string or a stringified line number). Runs any mutations along the way and then returns
31 | ## the first dialogue line encountered.
32 | func get_next_dialogue_line(title: String = "", extra_game_states: Array = [], mutation_behaviour: DMConstants.MutationBehaviour = DMConstants.MutationBehaviour.Wait) -> DialogueLine:
33 | return await Engine.get_singleton("DialogueManager").get_next_dialogue_line(self, title, extra_game_states, mutation_behaviour)
34 |
35 |
36 | ## Get the list of any titles found in the file.
37 | func get_titles() -> PackedStringArray:
38 | return titles.keys()
39 |
40 |
41 | func _to_string() -> String:
42 | return "" % [",".join(titles.keys())]
43 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/dialogue_resource.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dbs4435dsf3ry
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/dialogue_response.gd:
--------------------------------------------------------------------------------
1 | ## A response to a line of dialogue, usualy attached to a [code]DialogueLine[/code].
2 | class_name DialogueResponse extends RefCounted
3 |
4 |
5 | ## The ID of this response
6 | var id: String
7 |
8 | ## The internal type of this dialogue object, always set to [code]TYPE_RESPONSE[/code].
9 | var type: String = DMConstants.TYPE_RESPONSE
10 |
11 | ## The next line ID to use if this response is selected by the player.
12 | var next_id: String = ""
13 |
14 | ## [code]true[/code] if the condition of this line was met.
15 | var is_allowed: bool = true
16 |
17 | ## The original condition text.
18 | var condition_as_text: String = ""
19 |
20 | ## A character (depending on the "characters in responses" behaviour setting).
21 | var character: String = ""
22 |
23 | ## A dictionary of varialbe replaces for the character name. Generally for internal use only.
24 | var character_replacements: Array[Dictionary] = []
25 |
26 | ## The prompt for this response.
27 | var text: String = ""
28 |
29 | ## A dictionary of variable replaces for the text. Generally for internal use only.
30 | var text_replacements: Array[Dictionary] = []
31 |
32 | ## Any #tags
33 | var tags: PackedStringArray = []
34 |
35 | ## The key to use for translating the text.
36 | var translation_key: String = ""
37 |
38 |
39 | func _init(data: Dictionary = {}) -> void:
40 | if data.size() > 0:
41 | id = data.id
42 | type = data.type
43 | next_id = data.next_id
44 | is_allowed = data.is_allowed
45 | character = data.character
46 | character_replacements = data.character_replacements
47 | text = data.text
48 | text_replacements = data.text_replacements
49 | tags = data.tags
50 | translation_key = data.translation_key
51 | condition_as_text = data.condition_as_text
52 |
53 |
54 | func _to_string() -> String:
55 | return "" % text
56 |
57 |
58 | func get_tag_value(tag_name: String) -> String:
59 | var wrapped := "%s=" % tag_name
60 | for t in tags:
61 | if t.begins_with(wrapped):
62 | return t.replace(wrapped, "").strip_edges()
63 | return ""
64 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/dialogue_response.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cm0xpfeywpqid
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/dialogue_responses_menu.gd:
--------------------------------------------------------------------------------
1 | @icon("./assets/responses_menu.svg")
2 |
3 | ## A [Container] for dialogue responses provided by [b]Dialogue Manager[/b].
4 | class_name DialogueResponsesMenu extends Container
5 |
6 |
7 | ## Emitted when a response is selected.
8 | signal response_selected(response)
9 |
10 |
11 | ## Optionally specify a control to duplicate for each response
12 | @export var response_template: Control
13 |
14 | ## The action for accepting a response (is possibly overridden by parent dialogue balloon).
15 | @export var next_action: StringName = &""
16 |
17 | ## Hide any responses where [code]is_allowed[/code] is false
18 | @export var hide_failed_responses: bool = false
19 |
20 | ## The list of dialogue responses.
21 | var responses: Array = []:
22 | get:
23 | return responses
24 | set(value):
25 | responses = value
26 |
27 | # Remove any current items
28 | for item in get_children():
29 | if item == response_template: continue
30 |
31 | remove_child(item)
32 | item.queue_free()
33 |
34 | # Add new items
35 | if responses.size() > 0:
36 | for response in responses:
37 | if hide_failed_responses and not response.is_allowed: continue
38 |
39 | var item: Control
40 | if is_instance_valid(response_template):
41 | item = response_template.duplicate(DUPLICATE_GROUPS | DUPLICATE_SCRIPTS | DUPLICATE_SIGNALS)
42 | item.show()
43 | else:
44 | item = Button.new()
45 | item.name = "Response%d" % get_child_count()
46 | if not response.is_allowed:
47 | item.name = item.name + &"Disallowed"
48 | item.disabled = true
49 |
50 | # If the item has a response property then use that
51 | if "response" in item:
52 | item.response = response
53 | # Otherwise assume we can just set the text
54 | else:
55 | item.text = response.text
56 |
57 | item.set_meta("response", response)
58 |
59 | add_child(item)
60 |
61 | _configure_focus()
62 |
63 |
64 | func _ready() -> void:
65 | visibility_changed.connect(func():
66 | if visible and get_menu_items().size() > 0:
67 | var first_item: Control = get_menu_items()[0]
68 | if first_item.is_inside_tree():
69 | first_item.grab_focus()
70 | )
71 |
72 | if is_instance_valid(response_template):
73 | response_template.hide()
74 |
75 |
76 | ## Get the selectable items in the menu.
77 | func get_menu_items() -> Array:
78 | var items: Array = []
79 | for child in get_children():
80 | if not child.visible: continue
81 | if "Disallowed" in child.name: continue
82 | items.append(child)
83 |
84 | return items
85 |
86 |
87 | #region Internal
88 |
89 |
90 | # Prepare the menu for keyboard and mouse navigation.
91 | func _configure_focus() -> void:
92 | var items = get_menu_items()
93 | for i in items.size():
94 | var item: Control = items[i]
95 |
96 | item.focus_mode = Control.FOCUS_ALL
97 |
98 | item.focus_neighbor_left = item.get_path()
99 | item.focus_neighbor_right = item.get_path()
100 |
101 | if i == 0:
102 | item.focus_neighbor_top = item.get_path()
103 | item.focus_neighbor_left = item.get_path()
104 | item.focus_previous = item.get_path()
105 | else:
106 | item.focus_neighbor_top = items[i - 1].get_path()
107 | item.focus_neighbor_left = items[i - 1].get_path()
108 | item.focus_previous = items[i - 1].get_path()
109 |
110 | if i == items.size() - 1:
111 | item.focus_neighbor_bottom = item.get_path()
112 | item.focus_neighbor_right = item.get_path()
113 | item.focus_next = item.get_path()
114 | else:
115 | item.focus_neighbor_bottom = items[i + 1].get_path()
116 | item.focus_neighbor_right = items[i + 1].get_path()
117 | item.focus_next = items[i + 1].get_path()
118 |
119 | item.mouse_entered.connect(_on_response_mouse_entered.bind(item))
120 | item.gui_input.connect(_on_response_gui_input.bind(item, item.get_meta("response")))
121 |
122 | items[0].grab_focus()
123 |
124 |
125 | #endregion
126 |
127 | #region Signals
128 |
129 |
130 | func _on_response_mouse_entered(item: Control) -> void:
131 | if "Disallowed" in item.name: return
132 |
133 | item.grab_focus()
134 |
135 |
136 | func _on_response_gui_input(event: InputEvent, item: Control, response) -> void:
137 | if "Disallowed" in item.name: return
138 |
139 | if event is InputEventMouseButton and event.is_pressed() and event.button_index == MOUSE_BUTTON_LEFT:
140 | get_viewport().set_input_as_handled()
141 | response_selected.emit(response)
142 | elif event.is_action_pressed(&"ui_accept" if next_action.is_empty() else next_action) and item in get_menu_items():
143 | get_viewport().set_input_as_handled()
144 | response_selected.emit(response)
145 |
146 |
147 | #endregion
148 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/dialogue_responses_menu.gd.uid:
--------------------------------------------------------------------------------
1 | uid://bb52rsfwhkxbn
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/editor_translation_parser_plugin.gd:
--------------------------------------------------------------------------------
1 | class_name DMTranslationParserPlugin extends EditorTranslationParserPlugin
2 |
3 |
4 | ## Cached result of parsing a dialogue file.
5 | var data: DMCompilerResult
6 | ## List of characters that were added.
7 | var translated_character_names: PackedStringArray = []
8 | var translated_lines: Array[Dictionary] = []
9 |
10 |
11 | func _parse_file(path: String) -> Array[PackedStringArray]:
12 | var msgs: Array[PackedStringArray] = []
13 | var file: FileAccess = FileAccess.open(path, FileAccess.READ)
14 | var text: String = file.get_as_text()
15 |
16 | data = DMCompiler.compile_string(text, path)
17 |
18 | var known_keys: PackedStringArray = PackedStringArray([])
19 |
20 | # Add all character names if settings ask for it
21 | if DMSettings.get_setting(DMSettings.INCLUDE_CHARACTERS_IN_TRANSLATABLE_STRINGS_LIST, true):
22 | translated_character_names = [] as Array[DialogueLine]
23 | for character_name: String in data.character_names:
24 | if character_name in known_keys: continue
25 |
26 | known_keys.append(character_name)
27 |
28 | translated_character_names.append(character_name)
29 | msgs.append(PackedStringArray([character_name.replace('"', '\"'), "dialogue", "", DMConstants.translate("translation_plugin.character_name")]))
30 |
31 | # Add all dialogue lines and responses
32 | var dialogue: Dictionary = data.lines
33 | for key: String in dialogue.keys():
34 | var line: Dictionary = dialogue.get(key)
35 |
36 | if not line.type in [DMConstants.TYPE_DIALOGUE, DMConstants.TYPE_RESPONSE]: continue
37 |
38 | var translation_key: String = line.get(&"translation_key", line.text)
39 |
40 | if translation_key in known_keys: continue
41 |
42 | known_keys.append(translation_key)
43 | translated_lines.append(line)
44 | if translation_key == line.text:
45 | msgs.append(PackedStringArray([line.text.replace('"', '\"'), "", "", line.get("notes", "")]))
46 | else:
47 | msgs.append(PackedStringArray([line.text.replace('"', '\"'), line.translation_key.replace('"', '\"'), "", line.get("notes", "")]))
48 |
49 | return msgs
50 |
51 |
52 | func _get_recognized_extensions() -> PackedStringArray:
53 | return ["dialogue"]
54 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/editor_translation_parser_plugin.gd.uid:
--------------------------------------------------------------------------------
1 | uid://c6bya881h1egb
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/example_balloon/ExampleBalloon.cs:
--------------------------------------------------------------------------------
1 | using Godot;
2 | using Godot.Collections;
3 |
4 | namespace DialogueManagerRuntime
5 | {
6 | public partial class ExampleBalloon : CanvasLayer
7 | {
8 | [Export] public string NextAction = "ui_accept";
9 | [Export] public string SkipAction = "ui_cancel";
10 |
11 |
12 | Control balloon;
13 | RichTextLabel characterLabel;
14 | RichTextLabel dialogueLabel;
15 | VBoxContainer responsesMenu;
16 |
17 | Resource resource;
18 | Array temporaryGameStates = new Array();
19 | bool isWaitingForInput = false;
20 | bool willHideBalloon = false;
21 |
22 | DialogueLine dialogueLine;
23 | DialogueLine DialogueLine
24 | {
25 | get => dialogueLine;
26 | set
27 | {
28 | if (value == null)
29 | {
30 | QueueFree();
31 | return;
32 | }
33 |
34 | dialogueLine = value;
35 | ApplyDialogueLine();
36 | }
37 | }
38 |
39 | Timer MutationCooldown = new Timer();
40 |
41 |
42 | public override void _Ready()
43 | {
44 | balloon = GetNode("%Balloon");
45 | characterLabel = GetNode("%CharacterLabel");
46 | dialogueLabel = GetNode("%DialogueLabel");
47 | responsesMenu = GetNode("%ResponsesMenu");
48 |
49 | balloon.Hide();
50 |
51 | balloon.GuiInput += (@event) =>
52 | {
53 | if ((bool)dialogueLabel.Get("is_typing"))
54 | {
55 | bool mouseWasClicked = @event is InputEventMouseButton && (@event as InputEventMouseButton).ButtonIndex == MouseButton.Left && @event.IsPressed();
56 | bool skipButtonWasPressed = @event.IsActionPressed(SkipAction);
57 | if (mouseWasClicked || skipButtonWasPressed)
58 | {
59 | GetViewport().SetInputAsHandled();
60 | dialogueLabel.Call("skip_typing");
61 | return;
62 | }
63 | }
64 |
65 | if (!isWaitingForInput) return;
66 | if (dialogueLine.Responses.Count > 0) return;
67 |
68 | GetViewport().SetInputAsHandled();
69 |
70 | if (@event is InputEventMouseButton && @event.IsPressed() && (@event as InputEventMouseButton).ButtonIndex == MouseButton.Left)
71 | {
72 | Next(dialogueLine.NextId);
73 | }
74 | else if (@event.IsActionPressed(NextAction) && GetViewport().GuiGetFocusOwner() == balloon)
75 | {
76 | Next(dialogueLine.NextId);
77 | }
78 | };
79 |
80 | if (string.IsNullOrEmpty((string)responsesMenu.Get("next_action")))
81 | {
82 | responsesMenu.Set("next_action", NextAction);
83 | }
84 | responsesMenu.Connect("response_selected", Callable.From((DialogueResponse response) =>
85 | {
86 | Next(response.NextId);
87 | }));
88 |
89 |
90 | // Hide the balloon when a mutation is running
91 | MutationCooldown.Timeout += () =>
92 | {
93 | if (willHideBalloon)
94 | {
95 | willHideBalloon = false;
96 | balloon.Hide();
97 | }
98 | };
99 | AddChild(MutationCooldown);
100 |
101 | DialogueManager.Mutated += OnMutated;
102 | }
103 |
104 |
105 | public override void _ExitTree()
106 | {
107 | DialogueManager.Mutated -= OnMutated;
108 | }
109 |
110 |
111 | public override void _UnhandledInput(InputEvent @event)
112 | {
113 | // Only the balloon is allowed to handle input while it's showing
114 | GetViewport().SetInputAsHandled();
115 | }
116 |
117 |
118 | public override async void _Notification(int what)
119 | {
120 | // Detect a change of locale and update the current dialogue line to show the new language
121 | if (what == NotificationTranslationChanged && IsInstanceValid(dialogueLabel))
122 | {
123 | float visibleRatio = dialogueLabel.VisibleRatio;
124 | DialogueLine = await DialogueManager.GetNextDialogueLine(resource, DialogueLine.Id, temporaryGameStates);
125 | if (visibleRatio < 1.0f)
126 | {
127 | dialogueLabel.Call("skip_typing");
128 | }
129 | }
130 | }
131 |
132 |
133 | public async void Start(Resource dialogueResource, string title, Array extraGameStates = null)
134 | {
135 | temporaryGameStates = new Array { this } + (extraGameStates ?? new Array());
136 | isWaitingForInput = false;
137 | resource = dialogueResource;
138 |
139 | DialogueLine = await DialogueManager.GetNextDialogueLine(resource, title, temporaryGameStates);
140 | }
141 |
142 |
143 | public async void Next(string nextId)
144 | {
145 | DialogueLine = await DialogueManager.GetNextDialogueLine(resource, nextId, temporaryGameStates);
146 | }
147 |
148 |
149 | #region Helpers
150 |
151 |
152 | private async void ApplyDialogueLine()
153 | {
154 | MutationCooldown.Stop();
155 |
156 | isWaitingForInput = false;
157 | balloon.FocusMode = Control.FocusModeEnum.All;
158 | balloon.GrabFocus();
159 |
160 | // Set up the character name
161 | characterLabel.Visible = !string.IsNullOrEmpty(dialogueLine.Character);
162 | characterLabel.Text = Tr(dialogueLine.Character, "dialogue");
163 |
164 | // Set up the dialogue
165 | dialogueLabel.Hide();
166 | dialogueLabel.Set("dialogue_line", dialogueLine);
167 |
168 | // Set up the responses
169 | responsesMenu.Hide();
170 | responsesMenu.Set("responses", dialogueLine.Responses);
171 |
172 | // Type out the text
173 | balloon.Show();
174 | willHideBalloon = false;
175 | dialogueLabel.Show();
176 | if (!string.IsNullOrEmpty(dialogueLine.Text))
177 | {
178 | dialogueLabel.Call("type_out");
179 | await ToSignal(dialogueLabel, "finished_typing");
180 | }
181 |
182 | // Wait for input
183 | if (dialogueLine.Responses.Count > 0)
184 | {
185 | balloon.FocusMode = Control.FocusModeEnum.None;
186 | responsesMenu.Show();
187 | }
188 | else if (!string.IsNullOrEmpty(dialogueLine.Time))
189 | {
190 | float time = 0f;
191 | if (!float.TryParse(dialogueLine.Time, out time))
192 | {
193 | time = dialogueLine.Text.Length * 0.02f;
194 | }
195 | await ToSignal(GetTree().CreateTimer(time), "timeout");
196 | Next(dialogueLine.NextId);
197 | }
198 | else
199 | {
200 | isWaitingForInput = true;
201 | balloon.FocusMode = Control.FocusModeEnum.All;
202 | balloon.GrabFocus();
203 | }
204 | }
205 |
206 |
207 | #endregion
208 |
209 |
210 | #region signals
211 |
212 |
213 | private void OnMutated(Dictionary _mutation)
214 | {
215 | isWaitingForInput = false;
216 | willHideBalloon = true;
217 | MutationCooldown.Start(0.1f);
218 | }
219 |
220 |
221 | #endregion
222 | }
223 | }
224 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/example_balloon/ExampleBalloon.cs.uid:
--------------------------------------------------------------------------------
1 | uid://5b3w40kwakl3
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/example_balloon/example_balloon.gd:
--------------------------------------------------------------------------------
1 | class_name DialogueManagerExampleBalloon extends CanvasLayer
2 | ## A basic dialogue balloon for use with Dialogue Manager.
3 |
4 | ## The action to use for advancing the dialogue
5 | @export var next_action: StringName = &"ui_accept"
6 |
7 | ## The action to use to skip typing the dialogue
8 | @export var skip_action: StringName = &"ui_cancel"
9 |
10 | ## The dialogue resource
11 | var resource: DialogueResource
12 |
13 | ## Temporary game states
14 | var temporary_game_states: Array = []
15 |
16 | ## See if we are waiting for the player
17 | var is_waiting_for_input: bool = false
18 |
19 | ## See if we are running a long mutation and should hide the balloon
20 | var will_hide_balloon: bool = false
21 |
22 | ## A dictionary to store any ephemeral variables
23 | var locals: Dictionary = {}
24 |
25 | var _locale: String = TranslationServer.get_locale()
26 |
27 | ## The current line
28 | var dialogue_line: DialogueLine:
29 | set(value):
30 | if value:
31 | dialogue_line = value
32 | apply_dialogue_line()
33 | else:
34 | # The dialogue has finished so close the balloon
35 | queue_free()
36 | get:
37 | return dialogue_line
38 |
39 | ## A cooldown timer for delaying the balloon hide when encountering a mutation.
40 | var mutation_cooldown: Timer = Timer.new()
41 |
42 | ## The base balloon anchor
43 | @onready var balloon: Control = %Balloon
44 |
45 | ## The label showing the name of the currently speaking character
46 | @onready var character_label: RichTextLabel = %CharacterLabel
47 |
48 | ## The label showing the currently spoken dialogue
49 | @onready var dialogue_label: DialogueLabel = %DialogueLabel
50 |
51 | ## The menu of responses
52 | @onready var responses_menu: DialogueResponsesMenu = %ResponsesMenu
53 |
54 |
55 | func _ready() -> void:
56 | balloon.hide()
57 | Engine.get_singleton("DialogueManager").mutated.connect(_on_mutated)
58 |
59 | # If the responses menu doesn't have a next action set, use this one
60 | if responses_menu.next_action.is_empty():
61 | responses_menu.next_action = next_action
62 |
63 | mutation_cooldown.timeout.connect(_on_mutation_cooldown_timeout)
64 | add_child(mutation_cooldown)
65 |
66 |
67 | func _unhandled_input(_event: InputEvent) -> void:
68 | # Only the balloon is allowed to handle input while it's showing
69 | get_viewport().set_input_as_handled()
70 |
71 |
72 | func _notification(what: int) -> void:
73 | ## Detect a change of locale and update the current dialogue line to show the new language
74 | if what == NOTIFICATION_TRANSLATION_CHANGED and _locale != TranslationServer.get_locale() and is_instance_valid(dialogue_label):
75 | _locale = TranslationServer.get_locale()
76 | var visible_ratio = dialogue_label.visible_ratio
77 | self.dialogue_line = await resource.get_next_dialogue_line(dialogue_line.id)
78 | if visible_ratio < 1:
79 | dialogue_label.skip_typing()
80 |
81 |
82 | ## Start some dialogue
83 | func start(dialogue_resource: DialogueResource, title: String, extra_game_states: Array = []) -> void:
84 | temporary_game_states = [self] + extra_game_states
85 | is_waiting_for_input = false
86 | resource = dialogue_resource
87 | self.dialogue_line = await resource.get_next_dialogue_line(title, temporary_game_states)
88 |
89 |
90 | ## Apply any changes to the balloon given a new [DialogueLine].
91 | func apply_dialogue_line() -> void:
92 | mutation_cooldown.stop()
93 |
94 | is_waiting_for_input = false
95 | balloon.focus_mode = Control.FOCUS_ALL
96 | balloon.grab_focus()
97 |
98 | character_label.visible = not dialogue_line.character.is_empty()
99 | character_label.text = tr(dialogue_line.character, "dialogue")
100 |
101 | dialogue_label.hide()
102 | dialogue_label.dialogue_line = dialogue_line
103 |
104 | responses_menu.hide()
105 | responses_menu.responses = dialogue_line.responses
106 |
107 | # Show our balloon
108 | balloon.show()
109 | will_hide_balloon = false
110 |
111 | dialogue_label.show()
112 | if not dialogue_line.text.is_empty():
113 | dialogue_label.type_out()
114 | await dialogue_label.finished_typing
115 |
116 | # Wait for input
117 | if dialogue_line.responses.size() > 0:
118 | balloon.focus_mode = Control.FOCUS_NONE
119 | responses_menu.show()
120 | elif dialogue_line.time != "":
121 | var time = dialogue_line.text.length() * 0.02 if dialogue_line.time == "auto" else dialogue_line.time.to_float()
122 | await get_tree().create_timer(time).timeout
123 | next(dialogue_line.next_id)
124 | else:
125 | is_waiting_for_input = true
126 | balloon.focus_mode = Control.FOCUS_ALL
127 | balloon.grab_focus()
128 |
129 |
130 | ## Go to the next line
131 | func next(next_id: String) -> void:
132 | self.dialogue_line = await resource.get_next_dialogue_line(next_id, temporary_game_states)
133 |
134 |
135 | #region Signals
136 |
137 |
138 | func _on_mutation_cooldown_timeout() -> void:
139 | if will_hide_balloon:
140 | will_hide_balloon = false
141 | balloon.hide()
142 |
143 |
144 | func _on_mutated(_mutation: Dictionary) -> void:
145 | is_waiting_for_input = false
146 | will_hide_balloon = true
147 | mutation_cooldown.start(0.1)
148 |
149 |
150 | func _on_balloon_gui_input(event: InputEvent) -> void:
151 | # See if we need to skip typing of the dialogue
152 | if dialogue_label.is_typing:
153 | var mouse_was_clicked: bool = event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed()
154 | var skip_button_was_pressed: bool = event.is_action_pressed(skip_action)
155 | if mouse_was_clicked or skip_button_was_pressed:
156 | get_viewport().set_input_as_handled()
157 | dialogue_label.skip_typing()
158 | return
159 |
160 | if not is_waiting_for_input: return
161 | if dialogue_line.responses.size() > 0: return
162 |
163 | # When there are no response options the balloon itself is the clickable thing
164 | get_viewport().set_input_as_handled()
165 |
166 | if event is InputEventMouseButton and event.is_pressed() and event.button_index == MOUSE_BUTTON_LEFT:
167 | next(dialogue_line.next_id)
168 | elif event.is_action_pressed(next_action) and get_viewport().gui_get_focus_owner() == balloon:
169 | next(dialogue_line.next_id)
170 |
171 |
172 | func _on_responses_menu_response_selected(response: DialogueResponse) -> void:
173 | next(response.next_id)
174 |
175 |
176 | #endregion
177 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/example_balloon/example_balloon.gd.uid:
--------------------------------------------------------------------------------
1 | uid://d1wt4ma6055l8
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/example_balloon/example_balloon.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=9 format=3 uid="uid://73jm5qjy52vq"]
2 |
3 | [ext_resource type="Script" uid="uid://d1wt4ma6055l8" path="res://addons/dialogue_manager/example_balloon/example_balloon.gd" id="1_36de5"]
4 | [ext_resource type="PackedScene" uid="uid://ckvgyvclnwggo" path="res://addons/dialogue_manager/dialogue_label.tscn" id="2_a8ve6"]
5 | [ext_resource type="Script" uid="uid://bb52rsfwhkxbn" path="res://addons/dialogue_manager/dialogue_responses_menu.gd" id="3_72ixx"]
6 |
7 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_spyqn"]
8 | bg_color = Color(0, 0, 0, 1)
9 | border_width_left = 3
10 | border_width_top = 3
11 | border_width_right = 3
12 | border_width_bottom = 3
13 | border_color = Color(0.329412, 0.329412, 0.329412, 1)
14 | corner_radius_top_left = 5
15 | corner_radius_top_right = 5
16 | corner_radius_bottom_right = 5
17 | corner_radius_bottom_left = 5
18 |
19 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ri4m3"]
20 | bg_color = Color(0.121569, 0.121569, 0.121569, 1)
21 | border_width_left = 3
22 | border_width_top = 3
23 | border_width_right = 3
24 | border_width_bottom = 3
25 | border_color = Color(1, 1, 1, 1)
26 | corner_radius_top_left = 5
27 | corner_radius_top_right = 5
28 | corner_radius_bottom_right = 5
29 | corner_radius_bottom_left = 5
30 |
31 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_e0njw"]
32 | bg_color = Color(0, 0, 0, 1)
33 | border_width_left = 3
34 | border_width_top = 3
35 | border_width_right = 3
36 | border_width_bottom = 3
37 | border_color = Color(0.6, 0.6, 0.6, 1)
38 | corner_radius_top_left = 5
39 | corner_radius_top_right = 5
40 | corner_radius_bottom_right = 5
41 | corner_radius_bottom_left = 5
42 |
43 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qkmqt"]
44 | bg_color = Color(0, 0, 0, 1)
45 | border_width_left = 3
46 | border_width_top = 3
47 | border_width_right = 3
48 | border_width_bottom = 3
49 | corner_radius_top_left = 5
50 | corner_radius_top_right = 5
51 | corner_radius_bottom_right = 5
52 | corner_radius_bottom_left = 5
53 |
54 | [sub_resource type="Theme" id="Theme_qq3yp"]
55 | default_font_size = 20
56 | Button/styles/disabled = SubResource("StyleBoxFlat_spyqn")
57 | Button/styles/focus = SubResource("StyleBoxFlat_ri4m3")
58 | Button/styles/hover = SubResource("StyleBoxFlat_e0njw")
59 | Button/styles/normal = SubResource("StyleBoxFlat_e0njw")
60 | MarginContainer/constants/margin_bottom = 15
61 | MarginContainer/constants/margin_left = 30
62 | MarginContainer/constants/margin_right = 30
63 | MarginContainer/constants/margin_top = 15
64 | PanelContainer/styles/panel = SubResource("StyleBoxFlat_qkmqt")
65 |
66 | [node name="ExampleBalloon" type="CanvasLayer"]
67 | layer = 100
68 | script = ExtResource("1_36de5")
69 |
70 | [node name="Balloon" type="Control" parent="."]
71 | unique_name_in_owner = true
72 | layout_mode = 3
73 | anchors_preset = 15
74 | anchor_right = 1.0
75 | anchor_bottom = 1.0
76 | grow_horizontal = 2
77 | grow_vertical = 2
78 | theme = SubResource("Theme_qq3yp")
79 |
80 | [node name="MarginContainer" type="MarginContainer" parent="Balloon"]
81 | layout_mode = 1
82 | anchors_preset = 12
83 | anchor_top = 1.0
84 | anchor_right = 1.0
85 | anchor_bottom = 1.0
86 | offset_top = -219.0
87 | grow_horizontal = 2
88 | grow_vertical = 0
89 |
90 | [node name="PanelContainer" type="PanelContainer" parent="Balloon/MarginContainer"]
91 | clip_children = 2
92 | layout_mode = 2
93 | mouse_filter = 1
94 |
95 | [node name="MarginContainer" type="MarginContainer" parent="Balloon/MarginContainer/PanelContainer"]
96 | layout_mode = 2
97 |
98 | [node name="VBoxContainer" type="VBoxContainer" parent="Balloon/MarginContainer/PanelContainer/MarginContainer"]
99 | layout_mode = 2
100 |
101 | [node name="CharacterLabel" type="RichTextLabel" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/VBoxContainer"]
102 | unique_name_in_owner = true
103 | modulate = Color(1, 1, 1, 0.501961)
104 | layout_mode = 2
105 | mouse_filter = 1
106 | bbcode_enabled = true
107 | text = "Character"
108 | fit_content = true
109 | scroll_active = false
110 |
111 | [node name="DialogueLabel" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/VBoxContainer" instance=ExtResource("2_a8ve6")]
112 | unique_name_in_owner = true
113 | layout_mode = 2
114 | size_flags_vertical = 3
115 | text = "Dialogue..."
116 |
117 | [node name="ResponsesMenu" type="VBoxContainer" parent="Balloon" node_paths=PackedStringArray("response_template")]
118 | unique_name_in_owner = true
119 | layout_mode = 1
120 | anchors_preset = 8
121 | anchor_left = 0.5
122 | anchor_top = 0.5
123 | anchor_right = 0.5
124 | anchor_bottom = 0.5
125 | offset_left = -290.5
126 | offset_top = -35.0
127 | offset_right = 290.5
128 | offset_bottom = 35.0
129 | grow_horizontal = 2
130 | grow_vertical = 2
131 | size_flags_vertical = 8
132 | theme_override_constants/separation = 2
133 | alignment = 1
134 | script = ExtResource("3_72ixx")
135 | response_template = NodePath("ResponseExample")
136 |
137 | [node name="ResponseExample" type="Button" parent="Balloon/ResponsesMenu"]
138 | layout_mode = 2
139 | text = "Response example"
140 |
141 | [connection signal="gui_input" from="Balloon" to="." method="_on_balloon_gui_input"]
142 | [connection signal="response_selected" from="Balloon/ResponsesMenu" to="." method="_on_responses_menu_response_selected"]
143 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/example_balloon/small_example_balloon.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=10 format=3 uid="uid://13s5spsk34qu"]
2 |
3 | [ext_resource type="Script" uid="uid://d1wt4ma6055l8" path="res://addons/dialogue_manager/example_balloon/example_balloon.gd" id="1_s2gbs"]
4 | [ext_resource type="PackedScene" uid="uid://ckvgyvclnwggo" path="res://addons/dialogue_manager/dialogue_label.tscn" id="2_hfvdi"]
5 | [ext_resource type="Script" uid="uid://bb52rsfwhkxbn" path="res://addons/dialogue_manager/dialogue_responses_menu.gd" id="3_1j1j0"]
6 |
7 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_235ry"]
8 | content_margin_left = 6.0
9 | content_margin_top = 3.0
10 | content_margin_right = 6.0
11 | content_margin_bottom = 3.0
12 | bg_color = Color(0.0666667, 0.0666667, 0.0666667, 1)
13 | border_width_left = 1
14 | border_width_top = 1
15 | border_width_right = 1
16 | border_width_bottom = 1
17 | border_color = Color(0.345098, 0.345098, 0.345098, 1)
18 | corner_radius_top_left = 3
19 | corner_radius_top_right = 3
20 | corner_radius_bottom_right = 3
21 | corner_radius_bottom_left = 3
22 |
23 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ufjut"]
24 | content_margin_left = 6.0
25 | content_margin_top = 3.0
26 | content_margin_right = 6.0
27 | content_margin_bottom = 3.0
28 | bg_color = Color(0.227451, 0.227451, 0.227451, 1)
29 | border_width_left = 1
30 | border_width_top = 1
31 | border_width_right = 1
32 | border_width_bottom = 1
33 | border_color = Color(1, 1, 1, 1)
34 | corner_radius_top_left = 3
35 | corner_radius_top_right = 3
36 | corner_radius_bottom_right = 3
37 | corner_radius_bottom_left = 3
38 |
39 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_fcbqo"]
40 | content_margin_left = 6.0
41 | content_margin_top = 3.0
42 | content_margin_right = 6.0
43 | content_margin_bottom = 3.0
44 | bg_color = Color(0.0666667, 0.0666667, 0.0666667, 1)
45 | border_width_left = 1
46 | border_width_top = 1
47 | border_width_right = 1
48 | border_width_bottom = 1
49 | corner_radius_top_left = 3
50 | corner_radius_top_right = 3
51 | corner_radius_bottom_right = 3
52 | corner_radius_bottom_left = 3
53 |
54 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_t6i7a"]
55 | content_margin_left = 6.0
56 | content_margin_top = 3.0
57 | content_margin_right = 6.0
58 | content_margin_bottom = 3.0
59 | bg_color = Color(0.0666667, 0.0666667, 0.0666667, 1)
60 | border_width_left = 1
61 | border_width_top = 1
62 | border_width_right = 1
63 | border_width_bottom = 1
64 | corner_radius_top_left = 3
65 | corner_radius_top_right = 3
66 | corner_radius_bottom_right = 3
67 | corner_radius_bottom_left = 3
68 |
69 | [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_i6nbm"]
70 | bg_color = Color(0, 0, 0, 1)
71 | border_width_left = 1
72 | border_width_top = 1
73 | border_width_right = 1
74 | border_width_bottom = 1
75 | corner_radius_top_left = 3
76 | corner_radius_top_right = 3
77 | corner_radius_bottom_right = 3
78 | corner_radius_bottom_left = 3
79 |
80 | [sub_resource type="Theme" id="Theme_qq3yp"]
81 | default_font_size = 8
82 | Button/styles/disabled = SubResource("StyleBoxFlat_235ry")
83 | Button/styles/focus = SubResource("StyleBoxFlat_ufjut")
84 | Button/styles/hover = SubResource("StyleBoxFlat_fcbqo")
85 | Button/styles/normal = SubResource("StyleBoxFlat_t6i7a")
86 | MarginContainer/constants/margin_bottom = 4
87 | MarginContainer/constants/margin_left = 8
88 | MarginContainer/constants/margin_right = 8
89 | MarginContainer/constants/margin_top = 4
90 | PanelContainer/styles/panel = SubResource("StyleBoxFlat_i6nbm")
91 |
92 | [node name="ExampleBalloon" type="CanvasLayer"]
93 | layer = 100
94 | script = ExtResource("1_s2gbs")
95 |
96 | [node name="Balloon" type="Control" parent="."]
97 | unique_name_in_owner = true
98 | layout_mode = 3
99 | anchors_preset = 15
100 | anchor_right = 1.0
101 | anchor_bottom = 1.0
102 | grow_horizontal = 2
103 | grow_vertical = 2
104 | theme = SubResource("Theme_qq3yp")
105 |
106 | [node name="MarginContainer" type="MarginContainer" parent="Balloon"]
107 | layout_mode = 1
108 | anchors_preset = 12
109 | anchor_top = 1.0
110 | anchor_right = 1.0
111 | anchor_bottom = 1.0
112 | offset_top = -71.0
113 | grow_horizontal = 2
114 | grow_vertical = 0
115 |
116 | [node name="PanelContainer" type="PanelContainer" parent="Balloon/MarginContainer"]
117 | clip_children = 2
118 | layout_mode = 2
119 | mouse_filter = 1
120 |
121 | [node name="MarginContainer" type="MarginContainer" parent="Balloon/MarginContainer/PanelContainer"]
122 | layout_mode = 2
123 |
124 | [node name="VBoxContainer" type="VBoxContainer" parent="Balloon/MarginContainer/PanelContainer/MarginContainer"]
125 | layout_mode = 2
126 |
127 | [node name="CharacterLabel" type="RichTextLabel" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/VBoxContainer"]
128 | unique_name_in_owner = true
129 | modulate = Color(1, 1, 1, 0.501961)
130 | layout_mode = 2
131 | mouse_filter = 1
132 | bbcode_enabled = true
133 | text = "Character"
134 | fit_content = true
135 | scroll_active = false
136 |
137 | [node name="DialogueLabel" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/VBoxContainer" instance=ExtResource("2_hfvdi")]
138 | unique_name_in_owner = true
139 | layout_mode = 2
140 | size_flags_vertical = 3
141 | text = "Dialogue..."
142 |
143 | [node name="ResponsesMenu" type="VBoxContainer" parent="Balloon"]
144 | unique_name_in_owner = true
145 | layout_mode = 1
146 | anchors_preset = 8
147 | anchor_left = 0.5
148 | anchor_top = 0.5
149 | anchor_right = 0.5
150 | anchor_bottom = 0.5
151 | offset_left = -116.5
152 | offset_top = -9.0
153 | offset_right = 116.5
154 | offset_bottom = 9.0
155 | grow_horizontal = 2
156 | grow_vertical = 2
157 | size_flags_vertical = 8
158 | theme_override_constants/separation = 2
159 | script = ExtResource("3_1j1j0")
160 |
161 | [node name="ResponseExample" type="Button" parent="Balloon/ResponsesMenu"]
162 | layout_mode = 2
163 | text = "Response Example"
164 |
165 | [connection signal="gui_input" from="Balloon" to="." method="_on_balloon_gui_input"]
166 | [connection signal="response_selected" from="Balloon/ResponsesMenu" to="." method="_on_responses_menu_response_selected"]
167 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/export_plugin.gd:
--------------------------------------------------------------------------------
1 | class_name DMExportPlugin extends EditorExportPlugin
2 |
3 | const IGNORED_PATHS = [
4 | "/assets",
5 | "/components",
6 | "/views",
7 | "inspector_plugin",
8 | "test_scene"
9 | ]
10 |
11 |
12 | func _get_name() -> String:
13 | return "Dialogue Manager Export Plugin"
14 |
15 |
16 | func _export_file(path: String, type: String, features: PackedStringArray) -> void:
17 | var plugin_path: String = Engine.get_meta("DialogueManagerPlugin").get_plugin_path()
18 |
19 | # Ignore any editor stuff
20 | for ignored_path: String in IGNORED_PATHS:
21 | if path.begins_with(plugin_path + ignored_path):
22 | skip()
23 |
24 | # Ignore C# stuff it not using dotnet
25 | if path.begins_with(plugin_path) and not DMSettings.check_for_dotnet_solution() and path.ends_with(".cs"):
26 | skip()
27 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/export_plugin.gd.uid:
--------------------------------------------------------------------------------
1 | uid://sa55ra11ji2q
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/import_plugin.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | class_name DMImportPlugin extends EditorImportPlugin
3 |
4 |
5 | signal compiled_resource(resource: Resource)
6 |
7 |
8 | const COMPILER_VERSION = 15
9 |
10 |
11 | func _get_importer_name() -> String:
12 | return "dialogue_manager"
13 |
14 |
15 | func _get_format_version() -> int:
16 | return COMPILER_VERSION
17 |
18 |
19 | func _get_visible_name() -> String:
20 | return "Dialogue"
21 |
22 |
23 | func _get_import_order() -> int:
24 | return -1000
25 |
26 |
27 | func _get_priority() -> float:
28 | return 1000.0
29 |
30 |
31 | func _get_resource_type():
32 | return "Resource"
33 |
34 |
35 | func _get_recognized_extensions() -> PackedStringArray:
36 | return PackedStringArray(["dialogue"])
37 |
38 |
39 | func _get_save_extension():
40 | return "tres"
41 |
42 |
43 | func _get_preset_count() -> int:
44 | return 0
45 |
46 |
47 | func _get_preset_name(preset_index: int) -> String:
48 | return "Unknown"
49 |
50 |
51 | func _get_import_options(path: String, preset_index: int) -> Array:
52 | # When the options array is empty there is a misleading error on export
53 | # that actually means nothing so let's just have an invisible option.
54 | return [{
55 | name = "defaults",
56 | default_value = true
57 | }]
58 |
59 |
60 | func _get_option_visibility(path: String, option_name: StringName, options: Dictionary) -> bool:
61 | return false
62 |
63 |
64 | func _import(source_file: String, save_path: String, options: Dictionary, platform_variants: Array[String], gen_files: Array[String]) -> Error:
65 | var cache = Engine.get_meta("DMCache")
66 |
67 | # Get the raw file contents
68 | if not FileAccess.file_exists(source_file): return ERR_FILE_NOT_FOUND
69 |
70 | var file: FileAccess = FileAccess.open(source_file, FileAccess.READ)
71 | var raw_text: String = file.get_as_text()
72 |
73 | cache.file_content_changed.emit(source_file, raw_text)
74 |
75 | # Compile the text
76 | var result: DMCompilerResult = DMCompiler.compile_string(raw_text, source_file)
77 | if result.errors.size() > 0:
78 | printerr("%d errors found in %s" % [result.errors.size(), source_file])
79 | cache.add_errors_to_file(source_file, result.errors)
80 | return OK
81 |
82 | # Get the current addon version
83 | var config: ConfigFile = ConfigFile.new()
84 | config.load("res://addons/dialogue_manager/plugin.cfg")
85 | var version: String = config.get_value("plugin", "version")
86 |
87 | # Save the results to a resource
88 | var resource: DialogueResource = DialogueResource.new()
89 | resource.set_meta("dialogue_manager_version", version)
90 |
91 | resource.using_states = result.using_states
92 | resource.titles = result.titles
93 | resource.first_title = result.first_title
94 | resource.character_names = result.character_names
95 | resource.lines = result.lines
96 | resource.raw_text = result.raw_text
97 |
98 | # Clear errors and possibly trigger any cascade recompiles
99 | cache.add_file(source_file, result)
100 |
101 | var err: Error = ResourceSaver.save(resource, "%s.%s" % [save_path, _get_save_extension()])
102 |
103 | compiled_resource.emit(resource)
104 |
105 | # Recompile any dependencies
106 | var dependent_paths: PackedStringArray = cache.get_dependent_paths_for_reimport(source_file)
107 | for path in dependent_paths:
108 | append_import_external_resource(path)
109 |
110 | return err
111 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/import_plugin.gd.uid:
--------------------------------------------------------------------------------
1 | uid://dhwpj6ed8soyq
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/inspector_plugin.gd:
--------------------------------------------------------------------------------
1 | @tool
2 | class_name DMInspectorPlugin extends EditorInspectorPlugin
3 |
4 |
5 | const DialogueEditorProperty = preload("./components/editor_property/editor_property.gd")
6 |
7 |
8 | func _can_handle(object) -> bool:
9 | if object is GDScript: return false
10 | if not object is Node: return false
11 | if "name" in object and object.name == "Dialogue Manager": return false
12 | return true
13 |
14 |
15 | func _parse_property(object: Object, type, name: String, hint_type, hint_string: String, usage_flags: int, wide: bool) -> bool:
16 | if hint_string == "DialogueResource" or ("dialogue" in name.to_lower() and hint_string == "Resource"):
17 | var property_editor = DialogueEditorProperty.new()
18 | add_property_editor(name, property_editor)
19 | return true
20 |
21 | return false
22 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/inspector_plugin.gd.uid:
--------------------------------------------------------------------------------
1 | uid://0x31sbqbikov
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/l10n/translations.pot:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Project-Id-Version: Dialogue Manager\n"
4 | "MIME-Version: 1.0\n"
5 | "Content-Type: text/plain; charset=UTF-8\n"
6 | "Content-Transfer-Encoding: 8-bit\n"
7 |
8 | msgid "start_a_new_file"
9 | msgstr ""
10 |
11 | msgid "open_a_file"
12 | msgstr ""
13 |
14 | msgid "open.open"
15 | msgstr ""
16 |
17 | msgid "open.quick_open"
18 | msgstr ""
19 |
20 | msgid "open.no_recent_files"
21 | msgstr ""
22 |
23 | msgid "open.clear_recent_files"
24 | msgstr ""
25 |
26 | msgid "save_all_files"
27 | msgstr ""
28 |
29 | msgid "all"
30 | msgstr ""
31 |
32 | msgid "find_in_files"
33 | msgstr ""
34 |
35 | msgid "test_dialogue"
36 | msgstr ""
37 |
38 | msgid "test_dialogue_from_line"
39 | msgstr ""
40 |
41 | msgid "search_for_text"
42 | msgstr ""
43 |
44 | msgid "insert"
45 | msgstr ""
46 |
47 | msgid "translations"
48 | msgstr ""
49 |
50 | msgid "sponsor"
51 | msgstr ""
52 |
53 | msgid "show_support"
54 | msgstr ""
55 |
56 | msgid "docs"
57 | msgstr ""
58 |
59 | msgid "insert.wave_bbcode"
60 | msgstr ""
61 |
62 | msgid "insert.shake_bbcode"
63 | msgstr ""
64 |
65 | msgid "insert.typing_pause"
66 | msgstr ""
67 |
68 | msgid "insert.typing_speed_change"
69 | msgstr ""
70 |
71 | msgid "insert.auto_advance"
72 | msgstr ""
73 |
74 | msgid "insert.templates"
75 | msgstr ""
76 |
77 | msgid "insert.title"
78 | msgstr ""
79 |
80 | msgid "insert.dialogue"
81 | msgstr ""
82 |
83 | msgid "insert.response"
84 | msgstr ""
85 |
86 | msgid "insert.random_lines"
87 | msgstr ""
88 |
89 | msgid "insert.random_text"
90 | msgstr ""
91 |
92 | msgid "insert.actions"
93 | msgstr ""
94 |
95 | msgid "insert.jump"
96 | msgstr ""
97 |
98 | msgid "insert.end_dialogue"
99 | msgstr ""
100 |
101 | msgid "generate_line_ids"
102 | msgstr ""
103 |
104 | msgid "save_to_csv"
105 | msgstr ""
106 |
107 | msgid "import_from_csv"
108 | msgstr ""
109 |
110 | msgid "confirm_close"
111 | msgstr ""
112 |
113 | msgid "confirm_close.save"
114 | msgstr ""
115 |
116 | msgid "confirm_close.discard"
117 | msgstr ""
118 |
119 | msgid "buffer.save"
120 | msgstr ""
121 |
122 | msgid "buffer.save_as"
123 | msgstr ""
124 |
125 | msgid "buffer.close"
126 | msgstr ""
127 |
128 | msgid "buffer.close_all"
129 | msgstr ""
130 |
131 | msgid "buffer.close_other_files"
132 | msgstr ""
133 |
134 | msgid "buffer.copy_file_path"
135 | msgstr ""
136 |
137 | msgid "buffer.show_in_filesystem"
138 | msgstr ""
139 |
140 | msgid "n_of_n"
141 | msgstr ""
142 |
143 | msgid "search.find"
144 | msgstr ""
145 |
146 | msgid "search.find_all"
147 | msgstr ""
148 |
149 | msgid "search.placeholder"
150 | msgstr ""
151 |
152 | msgid "search.replace_placeholder"
153 | msgstr ""
154 |
155 | msgid "search.replace_selected"
156 | msgstr ""
157 |
158 | msgid "search.previous"
159 | msgstr ""
160 |
161 | msgid "search.next"
162 | msgstr ""
163 |
164 | msgid "search.match_case"
165 | msgstr ""
166 |
167 | msgid "search.toggle_replace"
168 | msgstr ""
169 |
170 | msgid "search.replace_with"
171 | msgstr ""
172 |
173 | msgid "search.replace"
174 | msgstr ""
175 |
176 | msgid "search.replace_all"
177 | msgstr ""
178 |
179 | msgid "files_list.filter"
180 | msgstr ""
181 |
182 | msgid "titles_list.filter"
183 | msgstr ""
184 |
185 | msgid "errors.key_not_found"
186 | msgstr ""
187 |
188 | msgid "errors.line_and_message"
189 | msgstr ""
190 |
191 | msgid "errors_in_script"
192 | msgstr ""
193 |
194 | msgid "errors_with_build"
195 | msgstr ""
196 |
197 | msgid "errors.import_errors"
198 | msgstr ""
199 |
200 | msgid "errors.already_imported"
201 | msgstr ""
202 |
203 | msgid "errors.duplicate_import"
204 | msgstr ""
205 |
206 | msgid "errors.unknown_using"
207 | msgstr ""
208 |
209 | msgid "errors.empty_title"
210 | msgstr ""
211 |
212 | msgid "errors.duplicate_title"
213 | msgstr ""
214 |
215 | msgid "errors.invalid_title_string"
216 | msgstr ""
217 |
218 | msgid "errors.invalid_title_number"
219 | msgstr ""
220 |
221 | msgid "errors.unknown_title"
222 | msgstr ""
223 |
224 | msgid "errors.jump_to_invalid_title"
225 | msgstr ""
226 |
227 | msgid "errors.title_has_no_content"
228 | msgstr ""
229 |
230 | msgid "errors.invalid_expression"
231 | msgstr ""
232 |
233 | msgid "errors.unexpected_condition"
234 | msgstr ""
235 |
236 | msgid "errors.duplicate_id"
237 | msgstr ""
238 |
239 | msgid "errors.missing_id"
240 | msgstr ""
241 |
242 | msgid "errors.invalid_indentation"
243 | msgstr ""
244 |
245 | msgid "errors.condition_has_no_content"
246 | msgstr ""
247 |
248 | msgid "errors.incomplete_expression"
249 | msgstr ""
250 |
251 | msgid "errors.invalid_expression_for_value"
252 | msgstr ""
253 |
254 | msgid "errors.file_not_found"
255 | msgstr ""
256 |
257 | msgid "errors.unexpected_end_of_expression"
258 | msgstr ""
259 |
260 | msgid "errors.unexpected_function"
261 | msgstr ""
262 |
263 | msgid "errors.unexpected_bracket"
264 | msgstr ""
265 |
266 | msgid "errors.unexpected_closing_bracket"
267 | msgstr ""
268 |
269 | msgid "errors.missing_closing_bracket"
270 | msgstr ""
271 |
272 | msgid "errors.unexpected_operator"
273 | msgstr ""
274 |
275 | msgid "errors.unexpected_comma"
276 | msgstr ""
277 |
278 | msgid "errors.unexpected_colon"
279 | msgstr ""
280 |
281 | msgid "errors.unexpected_dot"
282 | msgstr ""
283 |
284 | msgid "errors.unexpected_boolean"
285 | msgstr ""
286 |
287 | msgid "errors.unexpected_string"
288 | msgstr ""
289 |
290 | msgid "errors.unexpected_number"
291 | msgstr ""
292 |
293 | msgid "errors.unexpected_variable"
294 | msgstr ""
295 |
296 | msgid "errors.invalid_index"
297 | msgstr ""
298 |
299 | msgid "errors.unexpected_assignment"
300 | msgstr ""
301 |
302 | msgid "errors.expected_when_or_else"
303 | msgstr ""
304 |
305 | msgid "errors.only_one_else_allowed"
306 | msgstr ""
307 |
308 | msgid "errors.when_must_belong_to_match"
309 | msgstr ""
310 |
311 | msgid "errors.concurrent_line_without_origin"
312 | msgstr ""
313 |
314 | msgid "errors.goto_not_allowed_on_concurrect_lines"
315 | msgstr ""
316 |
317 | msgid "errors.unexpected_syntax_on_nested_dialogue_line"
318 | msgstr ""
319 |
320 | msgid "errors.err_nested_dialogue_invalid_jump"
321 | msgstr ""
322 |
323 | msgid "errors.unknown"
324 | msgstr ""
325 |
326 | msgid "update.available"
327 | msgstr ""
328 |
329 | msgid "update.is_available_for_download"
330 | msgstr ""
331 |
332 | msgid "update.downloading"
333 | msgstr ""
334 |
335 | msgid "update.download_update"
336 | msgstr ""
337 |
338 | msgid "update.needs_reload"
339 | msgstr ""
340 |
341 | msgid "update.reload_ok_button"
342 | msgstr ""
343 |
344 | msgid "update.reload_cancel_button"
345 | msgstr ""
346 |
347 | msgid "update.reload_project"
348 | msgstr ""
349 |
350 | msgid "update.release_notes"
351 | msgstr ""
352 |
353 | msgid "update.success"
354 | msgstr ""
355 |
356 | msgid "update.failed"
357 | msgstr ""
358 |
359 | msgid "runtime.no_resource"
360 | msgstr ""
361 |
362 | msgid "runtime.no_content"
363 | msgstr ""
364 |
365 | msgid "runtime.errors"
366 | msgstr ""
367 |
368 | msgid "runtime.error_detail"
369 | msgstr ""
370 |
371 | msgid "runtime.errors_see_details"
372 | msgstr ""
373 |
374 | msgid "runtime.invalid_expression"
375 | msgstr ""
376 |
377 | msgid "runtime.array_index_out_of_bounds"
378 | msgstr ""
379 |
380 | msgid "runtime.left_hand_size_cannot_be_assigned_to"
381 | msgstr ""
382 |
383 | msgid "runtime.key_not_found"
384 | msgstr ""
385 |
386 | msgid "runtime.property_not_found"
387 | msgstr ""
388 |
389 | msgid "runtime.property_not_found_missing_export"
390 | msgstr ""
391 |
392 | msgid "runtime.method_not_found"
393 | msgstr ""
394 |
395 | msgid "runtime.signal_not_found"
396 | msgstr ""
397 |
398 | msgid "runtime.method_not_callable"
399 | msgstr ""
400 |
401 | msgid "runtime.unknown_operator"
402 | msgstr ""
403 |
404 | msgid "runtime.unknown_autoload"
405 | msgstr ""
406 |
407 | msgid "runtime.something_went_wrong"
408 | msgstr ""
409 |
410 | msgid "runtime.expected_n_got_n_args"
411 | msgstr ""
412 |
413 | msgid "runtime.unsupported_array_type"
414 | msgstr ""
415 |
416 | msgid "runtime.dialogue_balloon_missing_start_method"
417 | msgstr ""
418 |
419 | msgid "runtime.top_level_states_share_name"
420 | msgstr ""
421 |
422 | msgid "translation_plugin.character_name"
423 | msgstr ""
--------------------------------------------------------------------------------
/addons/dialogue_manager/l10n/zh.po:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Project-Id-Version: Dialogue Manager\n"
4 | "POT-Creation-Date: \n"
5 | "PO-Revision-Date: \n"
6 | "Last-Translator: \n"
7 | "Language-Team: penghao123456、憨憨羊の宇航鸽鸽、ABShinri\n"
8 | "Language: zh\n"
9 | "MIME-Version: 1.0\n"
10 | "Content-Type: text/plain; charset=UTF-8\n"
11 | "Content-Transfer-Encoding: 8bit\n"
12 | "X-Generator: Poedit 3.4\n"
13 |
14 | msgid "start_a_new_file"
15 | msgstr "创建新文件"
16 |
17 | msgid "open_a_file"
18 | msgstr "打开已有文件"
19 |
20 | msgid "open.open"
21 | msgstr "打开……"
22 |
23 | msgid "open.no_recent_files"
24 | msgstr "无历史记录"
25 |
26 | msgid "open.clear_recent_files"
27 | msgstr "清空历史记录"
28 |
29 | msgid "save_all_files"
30 | msgstr "保存所有文件"
31 |
32 | msgid "find_in_files"
33 | msgstr "在文件中查找"
34 |
35 | msgid "test_dialogue"
36 | msgstr "测试对话"
37 |
38 | msgid "search_for_text"
39 | msgstr "查找……"
40 |
41 | msgid "insert"
42 | msgstr "插入"
43 |
44 | msgid "translations"
45 | msgstr "翻译"
46 |
47 | msgid "show_support"
48 | msgstr "支持 Dialogue Manager"
49 |
50 | msgid "docs"
51 | msgstr "文档"
52 |
53 | msgid "insert.wave_bbcode"
54 | msgstr "波浪效果"
55 |
56 | msgid "insert.shake_bbcode"
57 | msgstr "抖动效果"
58 |
59 | msgid "insert.typing_pause"
60 | msgstr "输入间隔"
61 |
62 | msgid "insert.typing_speed_change"
63 | msgstr "输入速度变更"
64 |
65 | msgid "insert.auto_advance"
66 | msgstr "自动切行"
67 |
68 | msgid "insert.templates"
69 | msgstr "模板"
70 |
71 | msgid "insert.title"
72 | msgstr "标题"
73 |
74 | msgid "insert.dialogue"
75 | msgstr "对话"
76 |
77 | msgid "insert.response"
78 | msgstr "回复选项"
79 |
80 | msgid "insert.random_lines"
81 | msgstr "随机行"
82 |
83 | msgid "insert.random_text"
84 | msgstr "随机文本"
85 |
86 | msgid "insert.actions"
87 | msgstr "操作"
88 |
89 | msgid "insert.jump"
90 | msgstr "标题间跳转"
91 |
92 | msgid "insert.end_dialogue"
93 | msgstr "结束对话"
94 |
95 | msgid "generate_line_ids"
96 | msgstr "生成行 ID"
97 |
98 | msgid "save_characters_to_csv"
99 | msgstr "保存角色到 CSV"
100 |
101 | msgid "save_to_csv"
102 | msgstr "生成 CSV"
103 |
104 | msgid "import_from_csv"
105 | msgstr "从 CSV 导入"
106 |
107 | msgid "confirm_close"
108 | msgstr "是否要保存到“{path}”?"
109 |
110 | msgid "confirm_close.save"
111 | msgstr "保存"
112 |
113 | msgid "confirm_close.discard"
114 | msgstr "不保存"
115 |
116 | msgid "buffer.save"
117 | msgstr "保存"
118 |
119 | msgid "buffer.save_as"
120 | msgstr "另存为……"
121 |
122 | msgid "buffer.close"
123 | msgstr "关闭"
124 |
125 | msgid "buffer.close_all"
126 | msgstr "全部关闭"
127 |
128 | msgid "buffer.close_other_files"
129 | msgstr "关闭其他文件"
130 |
131 | msgid "buffer.copy_file_path"
132 | msgstr "复制文件路径"
133 |
134 | msgid "buffer.show_in_filesystem"
135 | msgstr "在 Godot 侧边栏中显示"
136 |
137 | msgid "n_of_n"
138 | msgstr "第{index}个,共{total}个"
139 |
140 | msgid "search.find"
141 | msgstr "查找:"
142 |
143 | msgid "search.find_all"
144 | msgstr "查找全部..."
145 |
146 | msgid "search.placeholder"
147 | msgstr "请输入查找的内容"
148 |
149 | msgid "search.replace_placeholder"
150 | msgstr "请输入替换的内容"
151 |
152 | msgid "search.replace_selected"
153 | msgstr "替换勾选"
154 |
155 | msgid "search.previous"
156 | msgstr "查找上一个"
157 |
158 | msgid "search.next"
159 | msgstr "查找下一个"
160 |
161 | msgid "search.match_case"
162 | msgstr "大小写敏感"
163 |
164 | msgid "search.toggle_replace"
165 | msgstr "替换"
166 |
167 | msgid "search.replace_with"
168 | msgstr "替换为"
169 |
170 | msgid "search.replace"
171 | msgstr "替换"
172 |
173 | msgid "search.replace_all"
174 | msgstr "全部替换"
175 |
176 | msgid "files_list.filter"
177 | msgstr "查找文件"
178 |
179 | msgid "titles_list.filter"
180 | msgstr "查找标题"
181 |
182 | msgid "errors.key_not_found"
183 | msgstr "键“{key}”未找到"
184 |
185 | msgid "errors.line_and_message"
186 | msgstr "第{line}行第{colume}列发生错误:{message}"
187 |
188 | msgid "errors_in_script"
189 | msgstr "你的脚本中存在错误。请修复错误,然后重试。"
190 |
191 | msgid "errors_with_build"
192 | msgstr "请先解决 Dialogue 中的错误。"
193 |
194 | msgid "errors.import_errors"
195 | msgstr "被导入的文件存在问题。"
196 |
197 | msgid "errors.already_imported"
198 | msgstr "文件已被导入。"
199 |
200 | msgid "errors.duplicate_import"
201 | msgstr "导入名不能重复。"
202 |
203 | msgid "errors.empty_title"
204 | msgstr "标题名不能为空。"
205 |
206 | msgid "errors.duplicate_title"
207 | msgstr "标题名不能重复。"
208 |
209 | msgid "errors.invalid_title_string"
210 | msgstr "标题名无效。"
211 |
212 | msgid "errors.invalid_title_number"
213 | msgstr "标题不能以数字开始。"
214 |
215 | msgid "errors.unknown_title"
216 | msgstr "标题未定义。"
217 |
218 | msgid "errors.jump_to_invalid_title"
219 | msgstr "标题名无效。"
220 |
221 | msgid "errors.title_has_no_content"
222 | msgstr "目标标题为空。请替换为“=> END”。"
223 |
224 | msgid "errors.invalid_expression"
225 | msgstr "表达式无效。"
226 |
227 | msgid "errors.unexpected_condition"
228 | msgstr "未知条件。"
229 |
230 | msgid "errors.duplicate_id"
231 | msgstr "ID 重复。"
232 |
233 | msgid "errors.missing_id"
234 | msgstr "ID 不存在。"
235 |
236 | msgid "errors.invalid_indentation"
237 | msgstr "缩进无效。"
238 |
239 | msgid "errors.condition_has_no_content"
240 | msgstr "条件下方不能为空。"
241 |
242 | msgid "errors.incomplete_expression"
243 | msgstr "不完整的表达式。"
244 |
245 | msgid "errors.invalid_expression_for_value"
246 | msgstr "无效的赋值表达式。"
247 |
248 | msgid "errors.file_not_found"
249 | msgstr "文件不存在。"
250 |
251 | msgid "errors.unexpected_end_of_expression"
252 | msgstr "表达式 end 不应存在。"
253 |
254 | msgid "errors.unexpected_function"
255 | msgstr "函数不应存在。"
256 |
257 | msgid "errors.unexpected_bracket"
258 | msgstr "方括号不应存在。"
259 |
260 | msgid "errors.unexpected_closing_bracket"
261 | msgstr "方括号不应存在。"
262 |
263 | msgid "errors.missing_closing_bracket"
264 | msgstr "闭方括号不存在。"
265 |
266 | msgid "errors.unexpected_operator"
267 | msgstr "操作符不应存在。"
268 |
269 | msgid "errors.unexpected_comma"
270 | msgstr "逗号不应存在。"
271 |
272 | msgid "errors.unexpected_colon"
273 | msgstr "冒号不应存在。"
274 |
275 | msgid "errors.unexpected_dot"
276 | msgstr "句号不应存在。"
277 |
278 | msgid "errors.unexpected_boolean"
279 | msgstr "布尔值不应存在。"
280 |
281 | msgid "errors.unexpected_string"
282 | msgstr "字符串不应存在。"
283 |
284 | msgid "errors.unexpected_number"
285 | msgstr "数字不应存在。"
286 |
287 | msgid "errors.unexpected_variable"
288 | msgstr "标识符不应存在。"
289 |
290 | msgid "errors.invalid_index"
291 | msgstr "索引无效。"
292 |
293 | msgid "errors.unexpected_assignment"
294 | msgstr "不应在条件判断中使用 = ,应使用 == 。"
295 |
296 | msgid "errors.unknown"
297 | msgstr "语法错误。"
298 |
299 | msgid "update.available"
300 | msgstr "v{version} 更新可用。"
301 |
302 | msgid "update.is_available_for_download"
303 | msgstr "v%s 已经可以下载。"
304 |
305 | msgid "update.downloading"
306 | msgstr "正在下载更新……"
307 |
308 | msgid "update.download_update"
309 | msgstr "下载"
310 |
311 | msgid "update.needs_reload"
312 | msgstr "需要重新加载项目以应用更新。"
313 |
314 | msgid "update.reload_ok_button"
315 | msgstr "重新加载"
316 |
317 | msgid "update.reload_cancel_button"
318 | msgstr "暂不重新加载"
319 |
320 | msgid "update.reload_project"
321 | msgstr "重新加载"
322 |
323 | msgid "update.release_notes"
324 | msgstr "查看发行注记"
325 |
326 | msgid "update.success"
327 | msgstr "v{version} 已成功安装并应用。"
328 |
329 | msgid "update.failed"
330 | msgstr "更新失败。"
331 |
332 | msgid "runtime.no_resource"
333 | msgstr "找不到资源。"
334 |
335 | msgid "runtime.no_content"
336 | msgstr "资源“{file_path}”为空。"
337 |
338 | msgid "runtime.errors"
339 | msgstr "文件中存在{errrors}个错误。"
340 |
341 | msgid "runtime.error_detail"
342 | msgstr "第{index}行:{message}"
343 |
344 | msgid "runtime.errors_see_details"
345 | msgstr "文件中存在{errrors}个错误。请查看调试输出。"
346 |
347 | msgid "runtime.invalid_expression"
348 | msgstr "表达式“{expression}”无效:{error}"
349 |
350 | msgid "runtime.array_index_out_of_bounds"
351 | msgstr "数组索引“{index}”越界。(数组名:“{array}”)"
352 |
353 | msgid "runtime.left_hand_size_cannot_be_assigned_to"
354 | msgstr "表达式左侧的变量无法被赋值。"
355 |
356 | msgid "runtime.key_not_found"
357 | msgstr "键“{key}”在字典“{dictionary}”中不存在。"
358 |
359 | msgid "runtime.property_not_found"
360 | msgstr "“{property}”不存在。(全局变量:{states})"
361 |
362 | msgid "runtime.property_not_found_missing_export"
363 | msgstr "“{property}”不存在。(全局变量:{states})你可能需要添加一个修饰词 [Export]。"
364 |
365 | msgid "runtime.method_not_found"
366 | msgstr "“{method}”不存在。(全局变量:{states})"
367 |
368 | msgid "runtime.signal_not_found"
369 | msgstr "“{sighal_name}”不存在。(全局变量:{states})"
370 |
371 | msgid "runtime.method_not_callable"
372 | msgstr "{method}不是对象“{object}”上的函数。"
373 |
374 | msgid "runtime.unknown_operator"
375 | msgstr "未知操作符。"
376 |
377 | msgid "runtime.something_went_wrong"
378 | msgstr "有什么出错了。"
379 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/l10n/zh_TW.po:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Project-Id-Version: Dialogue Manager\n"
4 | "POT-Creation-Date: \n"
5 | "PO-Revision-Date: \n"
6 | "Last-Translator: \n"
7 | "Language-Team: 憨憨羊の宇航鴿鴿、ABShinri\n"
8 | "Language: zh_TW\n"
9 | "MIME-Version: 1.0\n"
10 | "Content-Type: text/plain; charset=UTF-8\n"
11 | "Content-Transfer-Encoding: 8bit\n"
12 | "X-Generator: Poedit 3.4\n"
13 |
14 | msgid "start_a_new_file"
15 | msgstr "創建新檔案"
16 |
17 | msgid "open_a_file"
18 | msgstr "開啟已有檔案"
19 |
20 | msgid "open.open"
21 | msgstr "開啟……"
22 |
23 | msgid "open.no_recent_files"
24 | msgstr "無歷史記錄"
25 |
26 | msgid "open.clear_recent_files"
27 | msgstr "清空歷史記錄"
28 |
29 | msgid "save_all_files"
30 | msgstr "儲存所有檔案"
31 |
32 | msgid "find_in_files"
33 | msgstr "在檔案中查找"
34 |
35 | msgid "test_dialogue"
36 | msgstr "測試對話"
37 |
38 | msgid "search_for_text"
39 | msgstr "搜尋……"
40 |
41 | msgid "insert"
42 | msgstr "插入"
43 |
44 | msgid "translations"
45 | msgstr "翻譯"
46 |
47 | msgid "show_support"
48 | msgstr "支援 Dialogue Manager"
49 |
50 | msgid "docs"
51 | msgstr "文檔"
52 |
53 | msgid "insert.wave_bbcode"
54 | msgstr "波浪特效"
55 |
56 | msgid "insert.shake_bbcode"
57 | msgstr "震動特效"
58 |
59 | msgid "insert.typing_pause"
60 | msgstr "輸入間隔"
61 |
62 | msgid "insert.typing_speed_change"
63 | msgstr "輸入速度變更"
64 |
65 | msgid "insert.auto_advance"
66 | msgstr "自動切行"
67 |
68 | msgid "insert.templates"
69 | msgstr "模板"
70 |
71 | msgid "insert.title"
72 | msgstr "標題"
73 |
74 | msgid "insert.dialogue"
75 | msgstr "對話"
76 |
77 | msgid "insert.response"
78 | msgstr "回覆選項"
79 |
80 | msgid "insert.random_lines"
81 | msgstr "隨機行"
82 |
83 | msgid "insert.random_text"
84 | msgstr "隨機文本"
85 |
86 | msgid "insert.actions"
87 | msgstr "操作"
88 |
89 | msgid "insert.jump"
90 | msgstr "標題間跳轉"
91 |
92 | msgid "insert.end_dialogue"
93 | msgstr "結束對話"
94 |
95 | msgid "generate_line_ids"
96 | msgstr "生成行 ID"
97 |
98 | msgid "save_characters_to_csv"
99 | msgstr "保存角色到 CSV"
100 |
101 | msgid "save_to_csv"
102 | msgstr "生成 CSV"
103 |
104 | msgid "import_from_csv"
105 | msgstr "從 CSV 匯入"
106 |
107 | msgid "confirm_close"
108 | msgstr "是否要儲存到“{path}”?"
109 |
110 | msgid "confirm_close.save"
111 | msgstr "儲存"
112 |
113 | msgid "confirm_close.discard"
114 | msgstr "不儲存"
115 |
116 | msgid "buffer.save"
117 | msgstr "儲存"
118 |
119 | msgid "buffer.save_as"
120 | msgstr "儲存爲……"
121 |
122 | msgid "buffer.close"
123 | msgstr "關閉"
124 |
125 | msgid "buffer.close_all"
126 | msgstr "全部關閉"
127 |
128 | msgid "buffer.close_other_files"
129 | msgstr "關閉其他檔案"
130 |
131 | msgid "buffer.copy_file_path"
132 | msgstr "複製檔案位置"
133 |
134 | msgid "buffer.show_in_filesystem"
135 | msgstr "在 Godot 側邊欄中顯示"
136 |
137 | msgid "n_of_n"
138 | msgstr "第{index}個,共{total}個"
139 |
140 | msgid "search.find"
141 | msgstr "搜尋:"
142 |
143 | msgid "search.find_all"
144 | msgstr "搜尋全部..."
145 |
146 | msgid "search.placeholder"
147 | msgstr "請輸入搜尋的內容"
148 |
149 | msgid "search.replace_placeholder"
150 | msgstr "請輸入替換的內容"
151 |
152 | msgid "search.replace_selected"
153 | msgstr "替換勾選"
154 |
155 | msgid "search.previous"
156 | msgstr "搜尋上一個"
157 |
158 | msgid "search.next"
159 | msgstr "搜尋下一個"
160 |
161 | msgid "search.match_case"
162 | msgstr "大小寫敏感"
163 |
164 | msgid "search.toggle_replace"
165 | msgstr "替換"
166 |
167 | msgid "search.replace_with"
168 | msgstr "替換爲"
169 |
170 | msgid "search.replace"
171 | msgstr "替換"
172 |
173 | msgid "search.replace_all"
174 | msgstr "全部替換"
175 |
176 | msgid "files_list.filter"
177 | msgstr "搜尋檔案"
178 |
179 | msgid "titles_list.filter"
180 | msgstr "搜尋標題"
181 |
182 | msgid "errors.key_not_found"
183 | msgstr "鍵“{key}”未找到"
184 |
185 | msgid "errors.line_and_message"
186 | msgstr "第{line}行第{colume}列發生錯誤:{message}"
187 |
188 | msgid "errors_in_script"
189 | msgstr "你的腳本中存在錯誤。請修復錯誤,然後重試。"
190 |
191 | msgid "errors_with_build"
192 | msgstr "請先解決 Dialogue 中的錯誤。"
193 |
194 | msgid "errors.import_errors"
195 | msgstr "被匯入的檔案存在問題。"
196 |
197 | msgid "errors.already_imported"
198 | msgstr "檔案已被匯入。"
199 |
200 | msgid "errors.duplicate_import"
201 | msgstr "匯入名不能重複。"
202 |
203 | msgid "errors.empty_title"
204 | msgstr "標題名不能爲空。"
205 |
206 | msgid "errors.duplicate_title"
207 | msgstr "標題名不能重複。"
208 |
209 | msgid "errors.invalid_title_string"
210 | msgstr "標題名無效。"
211 |
212 | msgid "errors.invalid_title_number"
213 | msgstr "標題不能以數字開始。"
214 |
215 | msgid "errors.unknown_title"
216 | msgstr "標題未定義。"
217 |
218 | msgid "errors.jump_to_invalid_title"
219 | msgstr "標題名無效。"
220 |
221 | msgid "errors.title_has_no_content"
222 | msgstr "目標標題爲空。請替換爲“=> END”。"
223 |
224 | msgid "errors.invalid_expression"
225 | msgstr "表達式無效。"
226 |
227 | msgid "errors.unexpected_condition"
228 | msgstr "未知條件。"
229 |
230 | msgid "errors.duplicate_id"
231 | msgstr "ID 重複。"
232 |
233 | msgid "errors.missing_id"
234 | msgstr "ID 不存在。"
235 |
236 | msgid "errors.invalid_indentation"
237 | msgstr "縮進無效。"
238 |
239 | msgid "errors.condition_has_no_content"
240 | msgstr "條件下方不能爲空。"
241 |
242 | msgid "errors.incomplete_expression"
243 | msgstr "不完整的表達式。"
244 |
245 | msgid "errors.invalid_expression_for_value"
246 | msgstr "無效的賦值表達式。"
247 |
248 | msgid "errors.file_not_found"
249 | msgstr "檔案不存在。"
250 |
251 | msgid "errors.unexpected_end_of_expression"
252 | msgstr "表達式 end 不應存在。"
253 |
254 | msgid "errors.unexpected_function"
255 | msgstr "函數不應存在。"
256 |
257 | msgid "errors.unexpected_bracket"
258 | msgstr "方括號不應存在。"
259 |
260 | msgid "errors.unexpected_closing_bracket"
261 | msgstr "方括號不應存在。"
262 |
263 | msgid "errors.missing_closing_bracket"
264 | msgstr "閉方括號不存在。"
265 |
266 | msgid "errors.unexpected_operator"
267 | msgstr "操作符不應存在。"
268 |
269 | msgid "errors.unexpected_comma"
270 | msgstr "逗號不應存在。"
271 |
272 | msgid "errors.unexpected_colon"
273 | msgstr "冒號不應存在。"
274 |
275 | msgid "errors.unexpected_dot"
276 | msgstr "句號不應存在。"
277 |
278 | msgid "errors.unexpected_boolean"
279 | msgstr "布爾值不應存在。"
280 |
281 | msgid "errors.unexpected_string"
282 | msgstr "字符串不應存在。"
283 |
284 | msgid "errors.unexpected_number"
285 | msgstr "數字不應存在。"
286 |
287 | msgid "errors.unexpected_variable"
288 | msgstr "標識符不應存在。"
289 |
290 | msgid "errors.invalid_index"
291 | msgstr "索引無效。"
292 |
293 | msgid "errors.unexpected_assignment"
294 | msgstr "不應在條件判斷中使用 = ,應使用 == 。"
295 |
296 | msgid "errors.unknown"
297 | msgstr "語法錯誤。"
298 |
299 | msgid "update.available"
300 | msgstr "v{version} 更新可用。"
301 |
302 | msgid "update.is_available_for_download"
303 | msgstr "v%s 已經可以下載。"
304 |
305 | msgid "update.downloading"
306 | msgstr "正在下載更新……"
307 |
308 | msgid "update.download_update"
309 | msgstr "下載"
310 |
311 | msgid "update.needs_reload"
312 | msgstr "需要重新加載項目以套用更新。"
313 |
314 | msgid "update.reload_ok_button"
315 | msgstr "重新加載"
316 |
317 | msgid "update.reload_cancel_button"
318 | msgstr "暫不重新加載"
319 |
320 | msgid "update.reload_project"
321 | msgstr "重新加載"
322 |
323 | msgid "update.release_notes"
324 | msgstr "查看發行註記"
325 |
326 | msgid "update.success"
327 | msgstr "v{version} 已成功安裝並套用。"
328 |
329 | msgid "update.failed"
330 | msgstr "更新失敗。"
331 |
332 | msgid "runtime.no_resource"
333 | msgstr "找不到資源。"
334 |
335 | msgid "runtime.no_content"
336 | msgstr "資源“{file_path}”爲空。"
337 |
338 | msgid "runtime.errors"
339 | msgstr "檔案中存在{errrors}個錯誤。"
340 |
341 | msgid "runtime.error_detail"
342 | msgstr "第{index}行:{message}"
343 |
344 | msgid "runtime.errors_see_details"
345 | msgstr "檔案中存在{errrors}個錯誤。請查看調試輸出。"
346 |
347 | msgid "runtime.invalid_expression"
348 | msgstr "表達式“{expression}”無效:{error}"
349 |
350 | msgid "runtime.array_index_out_of_bounds"
351 | msgstr "數組索引“{index}”越界。(數組名:“{array}”)"
352 |
353 | msgid "runtime.left_hand_size_cannot_be_assigned_to"
354 | msgstr "表達式左側的變量無法被賦值。"
355 |
356 | msgid "runtime.key_not_found"
357 | msgstr "鍵“{key}”在字典“{dictionary}”中不存在。"
358 |
359 | msgid "runtime.property_not_found"
360 | msgstr "“{property}”不存在。(全局變量:{states})"
361 |
362 | msgid "runtime.method_not_found"
363 | msgstr "“{method}”不存在。(全局變量:{states})"
364 |
365 | msgid "runtime.signal_not_found"
366 | msgstr "“{sighal_name}”不存在。(全局變量:{states})"
367 |
368 | msgid "runtime.property_not_found_missing_export"
369 | msgstr "“{property}”不存在。(全局變量:{states})你可能需要添加一個修飾詞 [Export]。"
370 |
371 | msgid "runtime.method_not_callable"
372 | msgstr "{method}不是對象“{object}”上的函數。"
373 |
374 | msgid "runtime.unknown_operator"
375 | msgstr "未知操作符。"
376 |
377 | msgid "runtime.something_went_wrong"
378 | msgstr "有什麼出錯了。"
379 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/plugin.cfg:
--------------------------------------------------------------------------------
1 | [plugin]
2 |
3 | name="Dialogue Manager"
4 | description="A powerful nonlinear dialogue system"
5 | author="Nathan Hoad"
6 | version="3.6.3"
7 | script="plugin.gd"
8 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/plugin.cfg.uid:
--------------------------------------------------------------------------------
1 | uid://hrny2utekhei
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/plugin.gd.uid:
--------------------------------------------------------------------------------
1 | uid://bpv426rpvrafa
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/settings.gd.uid:
--------------------------------------------------------------------------------
1 | uid://ce1nk88365m52
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/test_scene.gd:
--------------------------------------------------------------------------------
1 | class_name BaseDialogueTestScene extends Node2D
2 |
3 |
4 | const DialogueSettings = preload("./settings.gd")
5 | const DialogueResource = preload("./dialogue_resource.gd")
6 |
7 |
8 | @onready var title: String = DialogueSettings.get_user_value("run_title")
9 | @onready var resource: DialogueResource = load(DialogueSettings.get_user_value("run_resource_path"))
10 |
11 |
12 | func _ready():
13 | if not Engine.is_embedded_in_editor:
14 | var window: Window = get_viewport()
15 | var screen_index: int = DisplayServer.get_primary_screen()
16 | window.position = Vector2(DisplayServer.screen_get_position(screen_index)) + (DisplayServer.screen_get_size(screen_index) - window.size) * 0.5
17 | window.mode = Window.MODE_WINDOWED
18 |
19 | # Normally you can just call DialogueManager directly but doing so before the plugin has been
20 | # enabled in settings will throw a compiler error here so I'm using `get_singleton` instead.
21 | var dialogue_manager = Engine.get_singleton("DialogueManager")
22 | dialogue_manager.dialogue_ended.connect(_on_dialogue_ended)
23 | dialogue_manager.show_dialogue_balloon(resource, title if not title.is_empty() else resource.first_title)
24 |
25 |
26 | func _enter_tree() -> void:
27 | DialogueSettings.set_user_value("is_running_test_scene", false)
28 |
29 |
30 | #region Signals
31 |
32 |
33 | func _on_dialogue_ended(_resource: DialogueResource):
34 | get_tree().quit()
35 |
36 |
37 | #endregion
38 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/test_scene.gd.uid:
--------------------------------------------------------------------------------
1 | uid://c8e16qdgu40wo
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/test_scene.tscn:
--------------------------------------------------------------------------------
1 | [gd_scene load_steps=2 format=3 uid="uid://ugd552efvil0"]
2 |
3 | [ext_resource type="Script" uid="uid://c8e16qdgu40wo" path="res://addons/dialogue_manager/test_scene.gd" id="1_yupoh"]
4 |
5 | [node name="TestScene" type="Node2D"]
6 | script = ExtResource("1_yupoh")
7 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/utilities/builtins.gd.uid:
--------------------------------------------------------------------------------
1 | uid://bnfhuubdv5k20
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/utilities/dialogue_cache.gd:
--------------------------------------------------------------------------------
1 | class_name DMCache extends Node
2 |
3 |
4 | signal file_content_changed(path: String, new_content: String)
5 |
6 |
7 | # Keep track of errors and dependencies
8 | # {
9 | # = {
10 | # path = ,
11 | # dependencies = [, ],
12 | # errors = [, ]
13 | # }
14 | # }
15 | var _cache: Dictionary = {}
16 |
17 | var _update_dependency_timer: Timer = Timer.new()
18 | var _update_dependency_paths: PackedStringArray = []
19 |
20 | var _files_marked_for_reimport: PackedStringArray = []
21 |
22 |
23 | func _ready() -> void:
24 | add_child(_update_dependency_timer)
25 | _update_dependency_timer.timeout.connect(_on_update_dependency_timeout)
26 |
27 | _build_cache()
28 |
29 |
30 | func mark_files_for_reimport(files: PackedStringArray) -> void:
31 | for file in files:
32 | if not _files_marked_for_reimport.has(file):
33 | _files_marked_for_reimport.append(file)
34 |
35 |
36 | func reimport_files(and_files: PackedStringArray = []) -> void:
37 | for file in and_files:
38 | if not _files_marked_for_reimport.has(file):
39 | _files_marked_for_reimport.append(file)
40 |
41 | if _files_marked_for_reimport.is_empty(): return
42 |
43 | EditorInterface.get_resource_filesystem().reimport_files(_files_marked_for_reimport)
44 | _files_marked_for_reimport.clear()
45 |
46 |
47 | ## Add a dialogue file to the cache.
48 | func add_file(path: String, compile_result: DMCompilerResult = null) -> void:
49 | _cache[path] = {
50 | path = path,
51 | dependencies = [],
52 | errors = []
53 | }
54 |
55 | if compile_result != null:
56 | _cache[path].dependencies = Array(compile_result.imported_paths).filter(func(d): return d != path)
57 | _cache[path].compiled_at = Time.get_ticks_msec()
58 |
59 | # If this is a fresh cache entry, check for dependencies
60 | if compile_result == null and not _update_dependency_paths.has(path):
61 | queue_updating_dependencies(path)
62 |
63 |
64 | ## Get the file paths in the cache
65 | func get_files() -> PackedStringArray:
66 | return _cache.keys()
67 |
68 |
69 | ## Check if a file is known to the cache
70 | func has_file(path: String) -> bool:
71 | return _cache.has(path)
72 |
73 |
74 | ## Remember any errors in a dialogue file
75 | func add_errors_to_file(path: String, errors: Array[Dictionary]) -> void:
76 | if _cache.has(path):
77 | _cache[path].errors = errors
78 | else:
79 | _cache[path] = {
80 | path = path,
81 | resource_path = "",
82 | dependencies = [],
83 | errors = errors
84 | }
85 |
86 |
87 | ## Get a list of files that have errors
88 | func get_files_with_errors() -> Array[Dictionary]:
89 | var files_with_errors: Array[Dictionary] = []
90 | for dialogue_file in _cache.values():
91 | if dialogue_file and dialogue_file.errors.size() > 0:
92 | files_with_errors.append(dialogue_file)
93 | return files_with_errors
94 |
95 |
96 | ## Queue a file to have its dependencies checked
97 | func queue_updating_dependencies(of_path: String) -> void:
98 | _update_dependency_timer.stop()
99 | if not _update_dependency_paths.has(of_path):
100 | _update_dependency_paths.append(of_path)
101 | _update_dependency_timer.start(0.5)
102 |
103 |
104 | ## Update any references to a file path that has moved
105 | func move_file_path(from_path: String, to_path: String) -> void:
106 | if not _cache.has(from_path): return
107 |
108 | if to_path != "":
109 | _cache[to_path] = _cache[from_path].duplicate()
110 | _cache.erase(from_path)
111 |
112 |
113 | ## Get every dialogue file that imports on a file of a given path
114 | func get_files_with_dependency(imported_path: String) -> Array:
115 | return _cache.values().filter(func(d): return d.dependencies.has(imported_path))
116 |
117 |
118 | ## Get any paths that are dependent on a given path
119 | func get_dependent_paths_for_reimport(on_path: String) -> PackedStringArray:
120 | return get_files_with_dependency(on_path) \
121 | .filter(func(d): return Time.get_ticks_msec() - d.get("compiled_at", 0) > 3000) \
122 | .map(func(d): return d.path)
123 |
124 |
125 | # Build the initial cache for dialogue files
126 | func _build_cache() -> void:
127 | var current_files: PackedStringArray = _get_dialogue_files_in_filesystem()
128 | for file in current_files:
129 | add_file(file)
130 |
131 |
132 | # Recursively find any dialogue files in a directory
133 | func _get_dialogue_files_in_filesystem(path: String = "res://") -> PackedStringArray:
134 | var files: PackedStringArray = []
135 |
136 | if DirAccess.dir_exists_absolute(path):
137 | var dir = DirAccess.open(path)
138 | dir.list_dir_begin()
139 | var file_name = dir.get_next()
140 | while file_name != "":
141 | var file_path: String = (path + "/" + file_name).simplify_path()
142 | if dir.current_is_dir():
143 | if not file_name in [".godot", ".tmp"]:
144 | files.append_array(_get_dialogue_files_in_filesystem(file_path))
145 | elif file_name.get_extension() == "dialogue":
146 | files.append(file_path)
147 | file_name = dir.get_next()
148 |
149 | return files
150 |
151 |
152 | #region Signals
153 |
154 |
155 | func _on_update_dependency_timeout() -> void:
156 | _update_dependency_timer.stop()
157 | var import_regex: RegEx = RegEx.create_from_string("import \"(?.*?)\"")
158 | var file: FileAccess
159 | var found_imports: Array[RegExMatch]
160 | for path in _update_dependency_paths:
161 | # Open the file and check for any "import" lines
162 | file = FileAccess.open(path, FileAccess.READ)
163 | found_imports = import_regex.search_all(file.get_as_text())
164 | var dependencies: PackedStringArray = []
165 | for found in found_imports:
166 | dependencies.append(found.strings[found.names.path])
167 | _cache[path].dependencies = dependencies
168 | _update_dependency_paths.clear()
169 |
170 |
171 | #endregion
172 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/utilities/dialogue_cache.gd.uid:
--------------------------------------------------------------------------------
1 | uid://d3c83yd6bjp43
2 |
--------------------------------------------------------------------------------
/addons/dialogue_manager/views/main_view.gd.uid:
--------------------------------------------------------------------------------
1 | uid://cipjcc7bkh1pc
2 |
--------------------------------------------------------------------------------