Added dialogue manager plugin
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
uid://c4c5lsrwy3opj
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022-present Nathan Hoad and Dialogue Manager contributors.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
Binary file not shown.
@@ -0,0 +1,44 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://d1aawtj2vsnxx"
|
||||
path="res://.godot/imported/cue.svg-9417a7473d6bdedbede8aaea1abc9540.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/dialogue_manager/assets/cue.svg"
|
||||
dest_files=["res://.godot/imported/cue.svg-9417a7473d6bdedbede8aaea1abc9540.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
Binary file not shown.
@@ -0,0 +1,44 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://d3lr2uas6ax8v"
|
||||
path="res://.godot/imported/icon.svg-17eb5d3e2a3cfbe59852220758c5b7bd.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/dialogue_manager/assets/icon.svg"
|
||||
dest_files=["res://.godot/imported/icon.svg-17eb5d3e2a3cfbe59852220758c5b7bd.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=true
|
||||
Binary file not shown.
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cjl44xi6ekrqt"
|
||||
path="res://.godot/imported/logo.svg-f01e5595358a81255be3ce1c43f31397.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/dialogue_manager/assets/logo.svg"
|
||||
dest_files=["res://.godot/imported/logo.svg-f01e5595358a81255be3ce1c43f31397.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
Binary file not shown.
@@ -0,0 +1,44 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://qhp8cxd7n7yt"
|
||||
path="res://.godot/imported/region.svg-3bfc04b9f993118e54b176b232825952.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/dialogue_manager/assets/region.svg"
|
||||
dest_files=["res://.godot/imported/region.svg-3bfc04b9f993118e54b176b232825952.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
Binary file not shown.
@@ -0,0 +1,44 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c82jgt7nbtk7m"
|
||||
path="res://.godot/imported/resource.svg-c6e4a14848b9297c41a72f9fb208e7d0.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/dialogue_manager/assets/resource.svg"
|
||||
dest_files=["res://.godot/imported/resource.svg-c6e4a14848b9297c41a72f9fb208e7d0.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=true
|
||||
Binary file not shown.
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://d3baj6rygkb3f"
|
||||
path="res://.godot/imported/update.svg-f1628866ed4eb2e13e3b81f75443687e.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/dialogue_manager/assets/update.svg"
|
||||
dest_files=["res://.godot/imported/update.svg-f1628866ed4eb2e13e3b81f75443687e.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
uid://dsgpnyqg6cprg
|
||||
@@ -0,0 +1,161 @@
|
||||
## A compiled line of dialogue.
|
||||
class_name DMCompiledLine extends RefCounted
|
||||
|
||||
|
||||
## The ID of the line
|
||||
var id: String
|
||||
## The static line ID (or translation key).
|
||||
var static_id: String = ""
|
||||
## The type of line.
|
||||
var type: String = ""
|
||||
## The character name.
|
||||
var character: String = ""
|
||||
## Any interpolation expressions for the character name.
|
||||
var character_replacements: Array[Dictionary] = []
|
||||
## The text of the line.
|
||||
var text: String = ""
|
||||
## Any interpolation expressions for the text.
|
||||
var text_replacements: Array[Dictionary] = []
|
||||
## Any response siblings associated with this line.
|
||||
var responses: PackedStringArray = []
|
||||
## Any randomise or case siblings for this line.
|
||||
var siblings: Array[Dictionary] = []
|
||||
## Any lines said simultaneously.
|
||||
var concurrent_lines: PackedStringArray = []
|
||||
## Any tags on this line.
|
||||
var tags: PackedStringArray = []
|
||||
## The condition or mutation expression for this line.
|
||||
var expression: Dictionary = {}
|
||||
## The express as the raw text that was given.
|
||||
var expression_text: String = ""
|
||||
## The next sequential line to go to after this line.
|
||||
var next_id: String = ""
|
||||
## The next line to go to after this line if it is unknown and compile time.
|
||||
var next_id_expression: Array[Dictionary] = []
|
||||
## Whether this jump line should return after the jump target sequence has ended.
|
||||
var is_snippet: bool = false
|
||||
## The ID of the next sibling line.
|
||||
var next_sibling_id: String = ""
|
||||
## The ID after this line if it belongs to a block (eg. conditions).
|
||||
var next_id_after: String = ""
|
||||
## Any doc comments attached to this line.
|
||||
var notes: String = ""
|
||||
|
||||
|
||||
#region Hooks
|
||||
|
||||
|
||||
func _init(initial_id: String, initial_type: String) -> void:
|
||||
id = initial_id
|
||||
type = initial_type
|
||||
|
||||
|
||||
func _to_string() -> String:
|
||||
var s: Array = [
|
||||
"[%s]" % [type],
|
||||
"%s:" % [character] if character != "" else "",
|
||||
text if text != "" else "",
|
||||
str(expression) if expression.size() > 0 else "",
|
||||
"[%s]" % [",".join(tags)] if tags.size() > 0 else "",
|
||||
str(siblings) if siblings.size() > 0 else "",
|
||||
str(responses) if responses.size() > 0 else "",
|
||||
"=> END" if "end" in next_id else "=> %s" % [next_id],
|
||||
"(~> %s)" % [next_sibling_id] if next_sibling_id != "" else "",
|
||||
"(==> %s)" % [next_id_after] if next_id_after != "" else "",
|
||||
].filter(func(item: String) -> bool: return item != "")
|
||||
|
||||
return " ".join(s)
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
|
||||
## Express this line as a [Dictionary] that can be stored in a resource.
|
||||
func to_data() -> Dictionary:
|
||||
var d: Dictionary = {
|
||||
id = id,
|
||||
type = type,
|
||||
next_id = next_id
|
||||
}
|
||||
|
||||
if next_id_expression.size() > 0:
|
||||
d.next_id_expression = next_id_expression
|
||||
|
||||
match type:
|
||||
DMConstants.TYPE_CONDITION:
|
||||
d.condition = expression
|
||||
if not next_sibling_id.is_empty():
|
||||
d.next_sibling_id = next_sibling_id
|
||||
d.next_id_after = next_id_after
|
||||
|
||||
DMConstants.TYPE_WHILE:
|
||||
d.condition = expression
|
||||
d.next_id_after = next_id_after
|
||||
|
||||
DMConstants.TYPE_MATCH:
|
||||
d.condition = expression
|
||||
d.next_id_after = next_id_after
|
||||
d.cases = siblings
|
||||
|
||||
DMConstants.TYPE_MUTATION:
|
||||
d.mutation = expression
|
||||
|
||||
DMConstants.TYPE_GOTO:
|
||||
d.is_snippet = is_snippet
|
||||
d.next_id_after = next_id_after
|
||||
if not siblings.is_empty():
|
||||
d.siblings = siblings
|
||||
|
||||
DMConstants.TYPE_RANDOM:
|
||||
d.siblings = siblings
|
||||
|
||||
DMConstants.TYPE_RESPONSE:
|
||||
d.text = text
|
||||
|
||||
if not responses.is_empty():
|
||||
d.responses = responses
|
||||
|
||||
if static_id != text:
|
||||
d.static_id = static_id
|
||||
if not expression.is_empty():
|
||||
d.condition = expression
|
||||
if not character.is_empty():
|
||||
d.character = character
|
||||
if not character_replacements.is_empty():
|
||||
d.character_replacements = character_replacements
|
||||
if not text_replacements.is_empty():
|
||||
d.text_replacements = text_replacements
|
||||
if not tags.is_empty():
|
||||
d.tags = tags
|
||||
if not notes.is_empty():
|
||||
d.notes = notes
|
||||
if not expression_text.is_empty():
|
||||
d.condition_as_text = expression_text
|
||||
|
||||
DMConstants.TYPE_DIALOGUE:
|
||||
d.text = text
|
||||
|
||||
if static_id != text:
|
||||
d.static_id = static_id
|
||||
|
||||
if not character.is_empty():
|
||||
d.character = character
|
||||
if not character_replacements.is_empty():
|
||||
d.character_replacements = character_replacements
|
||||
if not text_replacements.is_empty():
|
||||
d.text_replacements = text_replacements
|
||||
if not tags.is_empty():
|
||||
d.tags = tags
|
||||
if not notes.is_empty():
|
||||
d.notes = notes
|
||||
if not siblings.is_empty():
|
||||
d.siblings = siblings
|
||||
if not concurrent_lines.is_empty():
|
||||
d.concurrent_lines = concurrent_lines
|
||||
|
||||
return d
|
||||
|
||||
|
||||
#endregion
|
||||
@@ -0,0 +1 @@
|
||||
uid://dg8j5hudp4210
|
||||
@@ -0,0 +1,57 @@
|
||||
## A compiler of Dialogue Manager dialogue.
|
||||
class_name DMCompiler extends RefCounted
|
||||
|
||||
|
||||
## Compile a dialogue script.
|
||||
static func compile_string(text: String, path: String) -> DMCompilerResult:
|
||||
var compilation: DMCompilation = DMCompilation.new()
|
||||
compilation.compile(text, path)
|
||||
|
||||
var result: DMCompilerResult = DMCompilerResult.new()
|
||||
result.imported_paths = compilation.imported_paths
|
||||
result.using_states = compilation.using_states
|
||||
result.character_names = compilation.character_names
|
||||
result.cues = compilation.cues
|
||||
result.first_cue = compilation.first_cue
|
||||
result.errors = compilation.errors
|
||||
result.lines = compilation.data
|
||||
|
||||
return result
|
||||
|
||||
|
||||
## Get the line type of a string. The returned string will match one of the [code]TYPE_[/code] constants of [DMConstants].
|
||||
static func get_line_type(text: String) -> String:
|
||||
var compilation: DMCompilation = DMCompilation.new()
|
||||
return compilation.get_line_type(text)
|
||||
|
||||
|
||||
## Get the static line ID (eg. [code][ID:SOMETHING][/code]) of some text.
|
||||
static func get_static_line_id(text: String) -> String:
|
||||
var compilation: DMCompilation = DMCompilation.new()
|
||||
return compilation.extract_static_line_id(text)
|
||||
|
||||
|
||||
## Get the translatable part of a line.
|
||||
static func extract_translatable_string(text: String) -> String:
|
||||
var compilation: DMCompilation = DMCompilation.new()
|
||||
|
||||
var tree_line: DMTreeLine = DMTreeLine.new("")
|
||||
tree_line.text = text
|
||||
var line: DMCompiledLine = DMCompiledLine.new("", compilation.get_line_type(text))
|
||||
compilation.parse_character_and_dialogue(tree_line, line, [tree_line], 0, null)
|
||||
|
||||
return line.text
|
||||
|
||||
|
||||
## Extract a mutation from a string.
|
||||
static func extract_mutation(text: String) -> Dictionary:
|
||||
var compilation: DMCompilation = DMCompilation.new()
|
||||
return compilation.extract_mutation(text)
|
||||
|
||||
|
||||
## Get the known cues in a dialogue script.
|
||||
static func get_cues_in_text(text: String, path: String) -> Dictionary:
|
||||
var compilation: DMCompilation = DMCompilation.new()
|
||||
compilation.find_imported_cues(text, path)
|
||||
compilation.build_line_tree(text.split("\n"))
|
||||
return compilation.cues
|
||||
@@ -0,0 +1 @@
|
||||
uid://chtfdmr0cqtp4
|
||||
@@ -0,0 +1,50 @@
|
||||
## A collection of [RegEx] for use by the [DMCompiler].
|
||||
class_name DMCompilerRegEx extends RefCounted
|
||||
|
||||
|
||||
var IMPORT_REGEX: RegEx = RegEx.create_from_string("import \"(?<path>[^\"]+)\" as (?<prefix>[a-zA-Z_\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]+)")
|
||||
var USING_REGEX: RegEx = RegEx.create_from_string("^using (?<state>.*)$")
|
||||
var INDENT_REGEX: RegEx = RegEx.create_from_string("^\\t+")
|
||||
var VALID_CUE_REGEX: RegEx = RegEx.create_from_string("^[a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]*$")
|
||||
var BEGINS_WITH_NUMBER_REGEX: RegEx = RegEx.create_from_string("^\\d")
|
||||
var CONDITION_REGEX: RegEx = RegEx.create_from_string("(if|elif|while|else if|match|when) (?<expression>.*)\\:?")
|
||||
var WRAPPED_CONDITION_REGEX: RegEx = RegEx.create_from_string("\\[if (?<expression>.*) \\/\\]")
|
||||
var MUTATION_REGEX: RegEx = RegEx.create_from_string("(?<keyword>\\$\\>|\\$\\>\\>|do|do!|set) (?<expression>.*)")
|
||||
var STATIC_LINE_ID_REGEX: RegEx = RegEx.create_from_string("\\[ID:(?<id>.*?)\\]")
|
||||
var WEIGHTED_RANDOM_SIBLINGS_REGEX: RegEx = RegEx.create_from_string("^\\%(?<weight>[\\d.]+)?( \\[if (?<condition>.+?) \\/\\])? ")
|
||||
var GOTO_REGEX: RegEx = RegEx.create_from_string("=><? (?<goto>.*)")
|
||||
|
||||
var INLINE_RANDOM_REGEX: RegEx = RegEx.create_from_string("\\[\\[(?<options>.*?)\\]\\]")
|
||||
var INLINE_CONDITIONALS_REGEX: RegEx = RegEx.create_from_string("\\[if (?<condition>.+?)\\](?<body>.*?)\\[\\/if\\]")
|
||||
|
||||
var IMAGE_TAGS_REGEX: RegEx = RegEx.create_from_string("\\[img.*?\\](?<path>.+?)\\[\\/img\\]")
|
||||
|
||||
var TAGS_REGEX: RegEx = RegEx.create_from_string("\\[#(?<tags>.*?)\\]")
|
||||
|
||||
var REPLACEMENTS_REGEX: RegEx = RegEx.create_from_string("{{(.*?)}}")
|
||||
|
||||
var TOKEN_DEFINITIONS: Dictionary = {
|
||||
DMConstants.TOKEN_FUNCTION: RegEx.create_from_string("^[a-zA-Z_\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]*\\("),
|
||||
DMConstants.TOKEN_DICTIONARY_REFERENCE: RegEx.create_from_string("^[a-zA-Z_\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]*\\["),
|
||||
DMConstants.TOKEN_PARENS_OPEN: RegEx.create_from_string("^\\("),
|
||||
DMConstants.TOKEN_PARENS_CLOSE: RegEx.create_from_string("^\\)"),
|
||||
DMConstants.TOKEN_BRACKET_OPEN: RegEx.create_from_string("^\\["),
|
||||
DMConstants.TOKEN_BRACKET_CLOSE: RegEx.create_from_string("^\\]"),
|
||||
DMConstants.TOKEN_BRACE_OPEN: RegEx.create_from_string("^\\{"),
|
||||
DMConstants.TOKEN_BRACE_CLOSE: RegEx.create_from_string("^\\}"),
|
||||
DMConstants.TOKEN_COLON: RegEx.create_from_string("^:"),
|
||||
DMConstants.TOKEN_COMPARISON: RegEx.create_from_string("^(==|<=|>=|<|>|!=|in )"),
|
||||
DMConstants.TOKEN_ASSIGNMENT: RegEx.create_from_string("^(\\+=|\\-=|\\*=|/=|=)"),
|
||||
DMConstants.TOKEN_NUMBER: RegEx.create_from_string("^\\-?\\d+(\\.\\d+)?"),
|
||||
DMConstants.TOKEN_OPERATOR: RegEx.create_from_string("^(\\+|\\-|\\*|/|%)"),
|
||||
DMConstants.TOKEN_COMMA: RegEx.create_from_string("^,"),
|
||||
DMConstants.TOKEN_NULL_COALESCE: RegEx.create_from_string("^\\?\\."),
|
||||
DMConstants.TOKEN_DOT: RegEx.create_from_string("^\\."),
|
||||
DMConstants.TOKEN_STRING: RegEx.create_from_string("^&?(\".*?\"|\'.*?\')"),
|
||||
DMConstants.TOKEN_NOT: RegEx.create_from_string("^(not( |$)|!)"),
|
||||
DMConstants.TOKEN_AND_OR: RegEx.create_from_string("^(and|or|&&|\\|\\|)( |$)"),
|
||||
DMConstants.TOKEN_VARIABLE: RegEx.create_from_string("^[a-zA-Z_\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]*"),
|
||||
DMConstants.TOKEN_COMMENT: RegEx.create_from_string("^#.*"),
|
||||
DMConstants.TOKEN_CONDITION: RegEx.create_from_string("^(if|elif|else)"),
|
||||
DMConstants.TOKEN_BOOL: RegEx.create_from_string("^(true|false)")
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://d3tvcrnicjibp
|
||||
@@ -0,0 +1,24 @@
|
||||
## The result of using the [DMCompiler] to compile some dialogue.
|
||||
class_name DMCompilerResult extends RefCounted
|
||||
|
||||
|
||||
## Any paths that were imported into the compiled dialogue file.
|
||||
var imported_paths: PackedStringArray = []
|
||||
|
||||
## Any "using" directives.
|
||||
var using_states: PackedStringArray = []
|
||||
|
||||
## All cues in the file and the line they point to.
|
||||
var cues: Dictionary = {}
|
||||
|
||||
## The first cue in the file.
|
||||
var first_cue: String = ""
|
||||
|
||||
## All character names.
|
||||
var character_names: PackedStringArray = []
|
||||
|
||||
## Any compilation errors.
|
||||
var errors: Array[DMError] = []
|
||||
|
||||
## A map of all compiled lines.
|
||||
var lines: Dictionary = {}
|
||||
@@ -0,0 +1 @@
|
||||
uid://dmk74tknimqvg
|
||||
@@ -0,0 +1,529 @@
|
||||
## A class for parsing a condition/mutation expression for use with the [DMCompiler].
|
||||
class_name DMExpressionParser extends RefCounted
|
||||
|
||||
|
||||
var include_comments: bool = false
|
||||
|
||||
|
||||
# Reference to the common [RegEx] that the parser needs.
|
||||
var regex: DMCompilerRegEx = DMCompilerRegEx.new()
|
||||
|
||||
|
||||
## Break a string down into an expression.
|
||||
func tokenise(text: String, line_type: String, index: int) -> Array:
|
||||
var tokens: Array[Dictionary] = []
|
||||
var limit: int = 0
|
||||
while text.strip_edges() != "" and limit < 1000:
|
||||
limit += 1
|
||||
var found: Dictionary = _find_match(text)
|
||||
if found.size() > 0:
|
||||
tokens.append({
|
||||
index = index,
|
||||
type = found.type,
|
||||
value = found.value
|
||||
})
|
||||
index += found.value.length()
|
||||
text = found.remaining_text
|
||||
elif text.begins_with(" "):
|
||||
index += 1
|
||||
text = text.substr(1)
|
||||
else:
|
||||
return _build_token_tree_error([], DMConstants.ERR_INVALID_EXPRESSION, index)
|
||||
|
||||
return _build_token_tree(tokens, line_type, "")[0]
|
||||
|
||||
|
||||
## Extract any expressions from some text
|
||||
func extract_replacements(text: String, index: int) -> Array[Dictionary]:
|
||||
var founds: Array[RegExMatch] = regex.REPLACEMENTS_REGEX.search_all(text)
|
||||
|
||||
if founds == null or founds.size() == 0:
|
||||
return []
|
||||
|
||||
var replacements: Array[Dictionary] = []
|
||||
for found: RegExMatch in founds:
|
||||
var replacement: Dictionary = {}
|
||||
var value_in_text: String = found.strings[0].substr(0, found.strings[0].length() - 2).substr(2)
|
||||
|
||||
# If there are closing curlie hard-up against the end of a {{...}} block then check for further
|
||||
# curlies just outside of the block.
|
||||
var text_suffix: String = text.substr(found.get_end(0))
|
||||
var expression_suffix: String = ""
|
||||
while text_suffix.begins_with("}"):
|
||||
expression_suffix += "}"
|
||||
text_suffix = text_suffix.substr(1)
|
||||
value_in_text += expression_suffix
|
||||
|
||||
var expression: Array = tokenise(value_in_text, DMConstants.TYPE_DIALOGUE, index + found.get_start(1))
|
||||
if expression.size() == 0:
|
||||
replacement = {
|
||||
index = index + found.get_start(1),
|
||||
error = DMConstants.ERR_INCOMPLETE_EXPRESSION
|
||||
}
|
||||
elif expression[0].type == DMConstants.TYPE_ERROR:
|
||||
replacement = {
|
||||
index = expression[0].i,
|
||||
error = expression[0].value
|
||||
}
|
||||
else:
|
||||
replacement = {
|
||||
value_in_text = "{{%s}}" % value_in_text,
|
||||
expression = expression
|
||||
}
|
||||
replacements.append(replacement)
|
||||
|
||||
return replacements
|
||||
|
||||
|
||||
#region Helpers
|
||||
|
||||
|
||||
# Create a token that represents an error.
|
||||
func _build_token_tree_error(tree: Array, error: int, index: int) -> Array:
|
||||
tree.insert(0, {
|
||||
type = DMConstants.TOKEN_ERROR,
|
||||
value = error,
|
||||
i = index
|
||||
})
|
||||
return tree
|
||||
|
||||
|
||||
# Convert a list of tokens into an abstract syntax tree.
|
||||
func _build_token_tree(tokens: Array[Dictionary], line_type: String, expected_close_token: String) -> Array:
|
||||
var tree: Array[Dictionary] = []
|
||||
var limit: int = 0
|
||||
while tokens.size() > 0 and limit < 1000:
|
||||
limit += 1
|
||||
var token: Dictionary = tokens.pop_front()
|
||||
|
||||
var error: int = _check_next_token(token, tokens, line_type, expected_close_token)
|
||||
if error != OK:
|
||||
var error_token: Dictionary = tokens[1] if tokens.size() > 1 else token
|
||||
return [_build_token_tree_error(tree, error, error_token.index), tokens]
|
||||
|
||||
match token.type:
|
||||
DMConstants.TOKEN_COMMENT:
|
||||
if include_comments:
|
||||
tree.append({
|
||||
type = DMConstants.TOKEN_COMMENT,
|
||||
value = token.value,
|
||||
i = token.index
|
||||
})
|
||||
|
||||
DMConstants.TOKEN_FUNCTION:
|
||||
var sub_tree: Array = _build_token_tree(tokens, line_type, DMConstants.TOKEN_PARENS_CLOSE)
|
||||
|
||||
if sub_tree[0].size() > 0 and sub_tree[0][0].type == DMConstants.TOKEN_ERROR:
|
||||
return [_build_token_tree_error(tree, sub_tree[0][0].value, sub_tree[0][0].i), tokens]
|
||||
|
||||
tree.append({
|
||||
type = DMConstants.TOKEN_FUNCTION,
|
||||
# Consume the trailing "("
|
||||
function = token.value.substr(0, token.value.length() - 1),
|
||||
value = _tokens_to_list(sub_tree[0]),
|
||||
i = token.index
|
||||
})
|
||||
tokens = sub_tree[1]
|
||||
|
||||
DMConstants.TOKEN_DICTIONARY_REFERENCE:
|
||||
var sub_tree: Array = _build_token_tree(tokens, line_type, DMConstants.TOKEN_BRACKET_CLOSE)
|
||||
|
||||
if sub_tree[0].size() > 0 and sub_tree[0][0].type == DMConstants.TOKEN_ERROR:
|
||||
return [_build_token_tree_error(tree, sub_tree[0][0].value, sub_tree[0][0].i), tokens]
|
||||
|
||||
var args: Array[Array] = _tokens_to_list(sub_tree[0])
|
||||
if args.size() != 1:
|
||||
return [_build_token_tree_error(tree, DMConstants.ERR_INVALID_INDEX, token.index), tokens]
|
||||
|
||||
tree.append({
|
||||
type = DMConstants.TOKEN_DICTIONARY_REFERENCE,
|
||||
# Consume the trailing "["
|
||||
variable = token.value.substr(0, token.value.length() - 1),
|
||||
value = args[0],
|
||||
i = token.index
|
||||
})
|
||||
tokens = sub_tree[1]
|
||||
|
||||
DMConstants.TOKEN_BRACE_OPEN:
|
||||
var sub_tree: Array = _build_token_tree(tokens, line_type, DMConstants.TOKEN_BRACE_CLOSE)
|
||||
|
||||
if sub_tree[0].size() > 0 and sub_tree[0][0].type == DMConstants.TOKEN_ERROR:
|
||||
return [_build_token_tree_error(tree, sub_tree[0][0].value, sub_tree[0][0].i), tokens]
|
||||
|
||||
var t: Array = sub_tree[0]
|
||||
for i: int in range(0, t.size() - 2):
|
||||
# Convert Lua style dictionaries to string keys
|
||||
if t[i].type == DMConstants.TOKEN_VARIABLE and t[i+1].type == DMConstants.TOKEN_ASSIGNMENT:
|
||||
t[i].type = DMConstants.TOKEN_STRING
|
||||
t[i+1].type = DMConstants.TOKEN_COLON
|
||||
t[i+1].erase("value")
|
||||
|
||||
tree.append({
|
||||
type = DMConstants.TOKEN_DICTIONARY,
|
||||
value = _tokens_to_dictionary(sub_tree[0]),
|
||||
i = token.index
|
||||
})
|
||||
|
||||
tokens = sub_tree[1]
|
||||
|
||||
DMConstants.TOKEN_BRACKET_OPEN:
|
||||
var sub_tree: Array = _build_token_tree(tokens, line_type, DMConstants.TOKEN_BRACKET_CLOSE)
|
||||
|
||||
if sub_tree[0].size() > 0 and sub_tree[0][0].type == DMConstants.TOKEN_ERROR:
|
||||
return [_build_token_tree_error(tree, sub_tree[0][0].value, sub_tree[0][0].i), tokens]
|
||||
|
||||
var type: StringName = DMConstants.TOKEN_ARRAY
|
||||
var value: Array[Array] = _tokens_to_list(sub_tree[0])
|
||||
|
||||
# See if this is referencing a nested dictionary value
|
||||
if tree.size() > 0:
|
||||
var previous_token: Dictionary = tree[tree.size() - 1]
|
||||
if previous_token.type in [DMConstants.TOKEN_DICTIONARY_REFERENCE, DMConstants.TOKEN_DICTIONARY_NESTED_REFERENCE]:
|
||||
type = DMConstants.TOKEN_DICTIONARY_NESTED_REFERENCE
|
||||
value = value[0]
|
||||
|
||||
tree.append({
|
||||
type = type,
|
||||
value = value,
|
||||
i = token.index
|
||||
})
|
||||
tokens = sub_tree[1]
|
||||
|
||||
DMConstants.TOKEN_PARENS_OPEN:
|
||||
var sub_tree: Array = _build_token_tree(tokens, line_type, DMConstants.TOKEN_PARENS_CLOSE)
|
||||
|
||||
if sub_tree[0].size() > 0 and sub_tree[0][0].type == DMConstants.TOKEN_ERROR:
|
||||
return [_build_token_tree_error(tree, sub_tree[0][0].value, sub_tree[0][0].i), tokens]
|
||||
|
||||
tree.append({
|
||||
type = DMConstants.TOKEN_GROUP,
|
||||
value = sub_tree[0],
|
||||
i = token.index
|
||||
})
|
||||
tokens = sub_tree[1]
|
||||
|
||||
DMConstants.TOKEN_PARENS_CLOSE, \
|
||||
DMConstants.TOKEN_BRACE_CLOSE, \
|
||||
DMConstants.TOKEN_BRACKET_CLOSE:
|
||||
if token.type != expected_close_token:
|
||||
return [_build_token_tree_error(tree, DMConstants.ERR_UNEXPECTED_CLOSING_BRACKET, token.index), tokens]
|
||||
|
||||
tree.append({
|
||||
type = token.type,
|
||||
i = token.index
|
||||
})
|
||||
|
||||
return [tree, tokens]
|
||||
|
||||
DMConstants.TOKEN_NOT:
|
||||
# Double nots negate each other
|
||||
if tokens.size() > 0 and tokens.front().type == DMConstants.TOKEN_NOT:
|
||||
tokens.pop_front()
|
||||
else:
|
||||
tree.append({
|
||||
type = token.type,
|
||||
i = token.index
|
||||
})
|
||||
|
||||
DMConstants.TOKEN_COMMA, \
|
||||
DMConstants.TOKEN_COLON, \
|
||||
DMConstants.TOKEN_DOT, \
|
||||
DMConstants.TOKEN_NULL_COALESCE:
|
||||
tree.append({
|
||||
type = token.type,
|
||||
i = token.index
|
||||
})
|
||||
|
||||
DMConstants.TOKEN_COMPARISON, \
|
||||
DMConstants.TOKEN_ASSIGNMENT, \
|
||||
DMConstants.TOKEN_OPERATOR, \
|
||||
DMConstants.TOKEN_AND_OR, \
|
||||
DMConstants.TOKEN_VARIABLE:
|
||||
var value: String = token.value.strip_edges()
|
||||
if value == "&&":
|
||||
value = "and"
|
||||
elif value == "||":
|
||||
value = "or"
|
||||
tree.append({
|
||||
type = token.type,
|
||||
value = value,
|
||||
i = token.index
|
||||
})
|
||||
|
||||
DMConstants.TOKEN_STRING:
|
||||
if token.value.begins_with("&"):
|
||||
tree.append({
|
||||
type = token.type,
|
||||
value = StringName(token.value.substr(2, token.value.length() - 3)),
|
||||
i = token.index
|
||||
})
|
||||
else:
|
||||
tree.append({
|
||||
type = token.type,
|
||||
value = token.value.substr(1, token.value.length() - 2),
|
||||
i = token.index
|
||||
})
|
||||
|
||||
DMConstants.TOKEN_CONDITION:
|
||||
return [_build_token_tree_error(tree, DMConstants.ERR_UNEXPECTED_CONDITION, token.index), token]
|
||||
|
||||
DMConstants.TOKEN_BOOL:
|
||||
tree.append({
|
||||
type = token.type,
|
||||
value = token.value.to_lower() == "true",
|
||||
i = token.index
|
||||
})
|
||||
|
||||
DMConstants.TOKEN_NUMBER:
|
||||
var value: Variant = token.value.to_float() if "." in token.value else token.value.to_int()
|
||||
# If previous token is a number and this one is a negative number then
|
||||
# inject a minus operator token in between them.
|
||||
if tree.size() > 0 and token.value.begins_with("-") and tree[tree.size() - 1].type == DMConstants.TOKEN_NUMBER:
|
||||
tree.append(({
|
||||
type = DMConstants.TOKEN_OPERATOR,
|
||||
value = "-",
|
||||
i = token.index
|
||||
}))
|
||||
tree.append({
|
||||
type = token.type,
|
||||
value = -1 * value,
|
||||
i = token.index
|
||||
})
|
||||
else:
|
||||
tree.append({
|
||||
type = token.type,
|
||||
value = value,
|
||||
i = token.index
|
||||
})
|
||||
|
||||
if expected_close_token != "":
|
||||
var index: int = tokens[0].i if tokens.size() > 0 else 0
|
||||
return [_build_token_tree_error(tree, DMConstants.ERR_MISSING_CLOSING_BRACKET, index), tokens]
|
||||
|
||||
return [tree, tokens]
|
||||
|
||||
|
||||
# Check the next token to see if it is valid to follow this one.
|
||||
func _check_next_token(token: Dictionary, next_tokens: Array[Dictionary], line_type: String, expected_close_token: String) -> int:
|
||||
var next_token: Dictionary = { type = null }
|
||||
if next_tokens.size() > 0:
|
||||
next_token = next_tokens.front()
|
||||
|
||||
# Guard for assigning in a condition. If the assignment token isn't inside a Lua dictionary
|
||||
# then it's an unexpected assignment in a condition line.
|
||||
if token.type == DMConstants.TOKEN_ASSIGNMENT and line_type == DMConstants.TYPE_CONDITION and not next_tokens.any(func(t: Dictionary) -> bool: return t.type == expected_close_token):
|
||||
return DMConstants.ERR_UNEXPECTED_ASSIGNMENT
|
||||
|
||||
# Special case for a negative number after this one
|
||||
if token.type == DMConstants.TOKEN_NUMBER and next_token.type == DMConstants.TOKEN_NUMBER and next_token.value.begins_with("-"):
|
||||
return OK
|
||||
|
||||
var expected_token_types: Array = []
|
||||
var unexpected_token_types: Array = []
|
||||
match token.type:
|
||||
DMConstants.TOKEN_FUNCTION, \
|
||||
DMConstants.TOKEN_PARENS_OPEN:
|
||||
unexpected_token_types = [
|
||||
null,
|
||||
DMConstants.TOKEN_COMMA,
|
||||
DMConstants.TOKEN_COLON,
|
||||
DMConstants.TOKEN_COMPARISON,
|
||||
DMConstants.TOKEN_ASSIGNMENT,
|
||||
DMConstants.TOKEN_OPERATOR,
|
||||
DMConstants.TOKEN_AND_OR,
|
||||
DMConstants.TOKEN_DOT
|
||||
]
|
||||
|
||||
DMConstants.TOKEN_BRACKET_CLOSE:
|
||||
unexpected_token_types = [
|
||||
DMConstants.TOKEN_NOT,
|
||||
DMConstants.TOKEN_BOOL,
|
||||
DMConstants.TOKEN_STRING,
|
||||
DMConstants.TOKEN_NUMBER,
|
||||
DMConstants.TOKEN_VARIABLE
|
||||
]
|
||||
|
||||
DMConstants.TOKEN_BRACE_OPEN:
|
||||
expected_token_types = [
|
||||
DMConstants.TOKEN_STRING,
|
||||
DMConstants.TOKEN_VARIABLE,
|
||||
DMConstants.TOKEN_NUMBER,
|
||||
DMConstants.TOKEN_BRACE_CLOSE
|
||||
]
|
||||
|
||||
DMConstants.TOKEN_PARENS_CLOSE, \
|
||||
DMConstants.TOKEN_BRACE_CLOSE:
|
||||
unexpected_token_types = [
|
||||
DMConstants.TOKEN_NOT,
|
||||
DMConstants.TOKEN_ASSIGNMENT,
|
||||
DMConstants.TOKEN_BOOL,
|
||||
DMConstants.TOKEN_STRING,
|
||||
DMConstants.TOKEN_NUMBER,
|
||||
DMConstants.TOKEN_VARIABLE
|
||||
]
|
||||
|
||||
DMConstants.TOKEN_COMPARISON, \
|
||||
DMConstants.TOKEN_OPERATOR, \
|
||||
DMConstants.TOKEN_DOT, \
|
||||
DMConstants.TOKEN_NULL_COALESCE, \
|
||||
DMConstants.TOKEN_NOT, \
|
||||
DMConstants.TOKEN_AND_OR, \
|
||||
DMConstants.TOKEN_DICTIONARY_REFERENCE:
|
||||
unexpected_token_types = [
|
||||
null,
|
||||
DMConstants.TOKEN_COMMA,
|
||||
DMConstants.TOKEN_COLON,
|
||||
DMConstants.TOKEN_COMPARISON,
|
||||
DMConstants.TOKEN_ASSIGNMENT,
|
||||
DMConstants.TOKEN_OPERATOR,
|
||||
DMConstants.TOKEN_AND_OR,
|
||||
DMConstants.TOKEN_PARENS_CLOSE,
|
||||
DMConstants.TOKEN_BRACE_CLOSE,
|
||||
DMConstants.TOKEN_BRACKET_CLOSE,
|
||||
DMConstants.TOKEN_DOT
|
||||
]
|
||||
|
||||
DMConstants.TOKEN_COMMA:
|
||||
unexpected_token_types = [
|
||||
null,
|
||||
DMConstants.TOKEN_COMMA,
|
||||
DMConstants.TOKEN_COLON,
|
||||
DMConstants.TOKEN_ASSIGNMENT,
|
||||
DMConstants.TOKEN_OPERATOR,
|
||||
DMConstants.TOKEN_AND_OR,
|
||||
DMConstants.TOKEN_PARENS_CLOSE,
|
||||
DMConstants.TOKEN_BRACE_CLOSE,
|
||||
DMConstants.TOKEN_BRACKET_CLOSE,
|
||||
DMConstants.TOKEN_DOT
|
||||
]
|
||||
|
||||
DMConstants.TOKEN_COLON:
|
||||
unexpected_token_types = [
|
||||
DMConstants.TOKEN_COMMA,
|
||||
DMConstants.TOKEN_COLON,
|
||||
DMConstants.TOKEN_COMPARISON,
|
||||
DMConstants.TOKEN_ASSIGNMENT,
|
||||
DMConstants.TOKEN_OPERATOR,
|
||||
DMConstants.TOKEN_AND_OR,
|
||||
DMConstants.TOKEN_PARENS_CLOSE,
|
||||
DMConstants.TOKEN_BRACE_CLOSE,
|
||||
DMConstants.TOKEN_BRACKET_CLOSE,
|
||||
DMConstants.TOKEN_DOT
|
||||
]
|
||||
|
||||
DMConstants.TOKEN_BOOL, \
|
||||
DMConstants.TOKEN_STRING, \
|
||||
DMConstants.TOKEN_NUMBER:
|
||||
unexpected_token_types = [
|
||||
DMConstants.TOKEN_NOT,
|
||||
DMConstants.TOKEN_ASSIGNMENT,
|
||||
DMConstants.TOKEN_BOOL,
|
||||
DMConstants.TOKEN_STRING,
|
||||
DMConstants.TOKEN_NUMBER,
|
||||
DMConstants.TOKEN_VARIABLE,
|
||||
DMConstants.TOKEN_FUNCTION,
|
||||
DMConstants.TOKEN_PARENS_OPEN,
|
||||
DMConstants.TOKEN_BRACE_OPEN,
|
||||
DMConstants.TOKEN_BRACKET_OPEN
|
||||
]
|
||||
|
||||
DMConstants.TOKEN_VARIABLE:
|
||||
unexpected_token_types = [
|
||||
DMConstants.TOKEN_NOT,
|
||||
DMConstants.TOKEN_BOOL,
|
||||
DMConstants.TOKEN_STRING,
|
||||
DMConstants.TOKEN_NUMBER,
|
||||
DMConstants.TOKEN_VARIABLE,
|
||||
DMConstants.TOKEN_FUNCTION,
|
||||
DMConstants.TOKEN_PARENS_OPEN,
|
||||
DMConstants.TOKEN_BRACE_OPEN,
|
||||
DMConstants.TOKEN_BRACKET_OPEN
|
||||
]
|
||||
|
||||
if (expected_token_types.size() > 0 and not next_token.type in expected_token_types) \
|
||||
or (unexpected_token_types.size() > 0 and next_token.type in unexpected_token_types):
|
||||
match next_token.type:
|
||||
null:
|
||||
return DMConstants.ERR_UNEXPECTED_END_OF_EXPRESSION
|
||||
|
||||
DMConstants.TOKEN_FUNCTION:
|
||||
return DMConstants.ERR_UNEXPECTED_FUNCTION
|
||||
|
||||
DMConstants.TOKEN_PARENS_OPEN, \
|
||||
DMConstants.TOKEN_PARENS_CLOSE:
|
||||
return DMConstants.ERR_UNEXPECTED_BRACKET
|
||||
|
||||
DMConstants.TOKEN_COMPARISON, \
|
||||
DMConstants.TOKEN_ASSIGNMENT, \
|
||||
DMConstants.TOKEN_OPERATOR, \
|
||||
DMConstants.TOKEN_NOT, \
|
||||
DMConstants.TOKEN_AND_OR:
|
||||
return DMConstants.ERR_UNEXPECTED_OPERATOR
|
||||
|
||||
DMConstants.TOKEN_COMMA:
|
||||
return DMConstants.ERR_UNEXPECTED_COMMA
|
||||
DMConstants.TOKEN_COLON:
|
||||
return DMConstants.ERR_UNEXPECTED_COLON
|
||||
DMConstants.TOKEN_DOT:
|
||||
return DMConstants.ERR_UNEXPECTED_DOT
|
||||
|
||||
DMConstants.TOKEN_BOOL:
|
||||
return DMConstants.ERR_UNEXPECTED_BOOLEAN
|
||||
DMConstants.TOKEN_STRING:
|
||||
return DMConstants.ERR_UNEXPECTED_STRING
|
||||
DMConstants.TOKEN_NUMBER:
|
||||
return DMConstants.ERR_UNEXPECTED_NUMBER
|
||||
DMConstants.TOKEN_VARIABLE:
|
||||
return DMConstants.ERR_UNEXPECTED_VARIABLE
|
||||
|
||||
return DMConstants.ERR_INVALID_EXPRESSION
|
||||
|
||||
return OK
|
||||
|
||||
|
||||
# Convert a series of comma separated tokens to an [Array].
|
||||
func _tokens_to_list(tokens: Array[Dictionary]) -> Array[Array]:
|
||||
var list: Array[Array] = []
|
||||
var current_item: Array[Dictionary] = []
|
||||
for token: Dictionary in tokens:
|
||||
if token.type == DMConstants.TOKEN_COMMA:
|
||||
list.append(current_item)
|
||||
current_item = []
|
||||
else:
|
||||
current_item.append(token)
|
||||
|
||||
if current_item.size() > 0:
|
||||
list.append(current_item)
|
||||
|
||||
return list
|
||||
|
||||
|
||||
# Convert a series of key/value tokens into a [Dictionary]
|
||||
func _tokens_to_dictionary(tokens: Array[Dictionary]) -> Dictionary:
|
||||
var dictionary: Dictionary = {}
|
||||
for i: int in range(0, tokens.size()):
|
||||
if tokens[i].type == DMConstants.TOKEN_COLON:
|
||||
if tokens.size() == i + 2:
|
||||
dictionary[tokens[i - 1]] = tokens[i + 1]
|
||||
else:
|
||||
dictionary[tokens[i - 1]] = { type = DMConstants.TOKEN_GROUP, value = tokens.slice(i + 1), i = tokens[0].i }
|
||||
|
||||
return dictionary
|
||||
|
||||
|
||||
# Work out what the next token is from a string.
|
||||
func _find_match(input: String) -> Dictionary:
|
||||
for key: String in regex.TOKEN_DEFINITIONS.keys():
|
||||
var token_regex: RegEx = regex.TOKEN_DEFINITIONS.get(key)
|
||||
var found: RegExMatch = token_regex.search(input)
|
||||
if found:
|
||||
return {
|
||||
type = key,
|
||||
remaining_text = input.substr(found.strings[0].length()),
|
||||
value = found.strings[0]
|
||||
}
|
||||
|
||||
return {}
|
||||
|
||||
|
||||
#endregion
|
||||
@@ -0,0 +1 @@
|
||||
uid://dbi4hbar8ubwu
|
||||
@@ -0,0 +1,70 @@
|
||||
## Data associated with a dialogue jump/goto line.
|
||||
class_name DMResolvedGotoData extends RefCounted
|
||||
|
||||
|
||||
## The cue that was specified
|
||||
var cue: String = ""
|
||||
## The target line's ID
|
||||
var next_id: String = ""
|
||||
## An expression to determine the target line at runtime.
|
||||
var expression: Array[Dictionary] = []
|
||||
## The given line text with the jump syntax removed.
|
||||
var text_without_goto: String = ""
|
||||
## Whether this is a jump-and-return style jump.
|
||||
var is_snippet: bool = false
|
||||
## A parse error if there was one.
|
||||
var error: int
|
||||
## The index in the string where
|
||||
var index: int = 0
|
||||
|
||||
# An instance of the compiler [RegEx] list.
|
||||
var regex: DMCompilerRegEx = DMCompilerRegEx.new()
|
||||
|
||||
|
||||
func _init(text: String, cues: Dictionary) -> void:
|
||||
if not "=> " in text and not "=>< " in text: return
|
||||
|
||||
if "=> " in text:
|
||||
text_without_goto = text.substr(0, text.find("=> ")).strip_edges()
|
||||
elif "=>< " in text:
|
||||
is_snippet = true
|
||||
text_without_goto = text.substr(0, text.find("=>< ")).strip_edges()
|
||||
|
||||
var found: RegExMatch = regex.GOTO_REGEX.search(text)
|
||||
if found == null:
|
||||
return
|
||||
|
||||
cue = found.strings[found.names.goto].strip_edges()
|
||||
index = found.get_start(0)
|
||||
|
||||
if cue == "":
|
||||
error = DMConstants.ERR_UNKNOWN_CUE
|
||||
return
|
||||
|
||||
# "=> END!" means end the conversation, ignoring any "=><" chains.
|
||||
if cue == "END!":
|
||||
next_id = DMConstants.ID_END_CONVERSATION
|
||||
|
||||
# "=> END" means end the current cue (and go back to the previous one if there is one
|
||||
# in the stack)
|
||||
elif cue == "END":
|
||||
next_id = DMConstants.ID_END
|
||||
|
||||
elif cues.has(cue):
|
||||
next_id = cues.get(cue)
|
||||
elif cue.begins_with("{{"):
|
||||
var expression_parser: DMExpressionParser = DMExpressionParser.new()
|
||||
var cue_expression: Array[Dictionary] = expression_parser.extract_replacements(cue, 0)
|
||||
if cue_expression.size() == 0:
|
||||
error = DMConstants.ERR_INCOMPLETE_EXPRESSION
|
||||
elif cue_expression[0].has("error"):
|
||||
error = cue_expression[0].error
|
||||
else:
|
||||
expression = cue_expression[0].expression
|
||||
else:
|
||||
next_id = cue
|
||||
error = DMConstants.ERR_UNKNOWN_CUE
|
||||
|
||||
|
||||
func _to_string() -> String:
|
||||
return "%s =>%s %s (%s)" % [text_without_goto, "<" if is_snippet else "", cue, next_id]
|
||||
@@ -0,0 +1 @@
|
||||
uid://llhl5pt47eoq
|
||||
@@ -0,0 +1,170 @@
|
||||
## Any data associated with inline dialogue BBCodes.
|
||||
class_name DMResolvedLineData extends RefCounted
|
||||
|
||||
## The line's text
|
||||
var text: String = ""
|
||||
## A map of speed changes against where they are found in the text.
|
||||
var speeds: Dictionary = {}
|
||||
## A list of any mutations to run and where they are found in the text.
|
||||
var mutations: Array[Array] = []
|
||||
## A duration reference for the line. Represented as "auto" or a stringified number.
|
||||
var time: String = ""
|
||||
|
||||
|
||||
func _init(line: String) -> void:
|
||||
text = line
|
||||
speeds = {}
|
||||
mutations = []
|
||||
time = ""
|
||||
|
||||
var bbcodes: Array = []
|
||||
|
||||
# Remove any escaped brackets (ie. "\[")
|
||||
var escaped_open_brackets: PackedInt32Array = []
|
||||
var escaped_close_brackets: PackedInt32Array = []
|
||||
for i: int in range(0, text.length() - 1):
|
||||
if text.substr(i, 2) == "\\[":
|
||||
text = text.substr(0, i) + "!" + text.substr(i + 2)
|
||||
escaped_open_brackets.append(i)
|
||||
elif text.substr(i, 2) == "\\]":
|
||||
text = text.substr(0, i) + "!" + text.substr(i + 2)
|
||||
escaped_close_brackets.append(i)
|
||||
|
||||
# Extract all of the BB codes so that we know the actual text (we could do this easier with
|
||||
# a RichTextLabel but then we'd need to await idle_frame which is annoying)
|
||||
var bbcode_positions: Array[Dictionary] = find_bbcode_positions_in_string(text)
|
||||
var accumulaive_length_offset: int = 0
|
||||
for position: Dictionary in bbcode_positions:
|
||||
# Ignore our own markers
|
||||
if position.code in ["wait", "speed", "/speed", "$>", "$>>", "do", "do!", "set", "next", "if", "else", "/if"]:
|
||||
continue
|
||||
|
||||
bbcodes.append({
|
||||
bbcode = position.bbcode,
|
||||
start = position.start,
|
||||
offset_start = position.start - accumulaive_length_offset
|
||||
})
|
||||
accumulaive_length_offset += position.bbcode.length()
|
||||
|
||||
for bb: Dictionary in bbcodes:
|
||||
text = text.substr(0, bb.offset_start) + text.substr(bb.offset_start + bb.bbcode.length())
|
||||
|
||||
# Now find any dialogue markers
|
||||
var next_bbcode_position: Array[Dictionary] = find_bbcode_positions_in_string(text, false)
|
||||
var limit: int = 0
|
||||
while next_bbcode_position.size() > 0 and limit < 1000:
|
||||
limit += 1
|
||||
|
||||
var bbcode: Dictionary = next_bbcode_position[0]
|
||||
var index: int = bbcode.start
|
||||
var code: String = bbcode.code
|
||||
var raw_args: String = bbcode.raw_args
|
||||
var args: Dictionary = {}
|
||||
if code in ["$>", "$>>", "do", "do!", "set"]:
|
||||
args["value"] = DMCompiler.extract_mutation("%s %s" % [code, raw_args])
|
||||
else:
|
||||
# Could be something like:
|
||||
# "=1.0"
|
||||
# " rate=20 level=10"
|
||||
if raw_args and raw_args[0] == "=":
|
||||
raw_args = "value" + raw_args
|
||||
for pair: String in raw_args.strip_edges().split(" "):
|
||||
if "=" in pair:
|
||||
var bits: PackedStringArray = pair.split("=")
|
||||
args[bits[0]] = bits[1]
|
||||
|
||||
match code:
|
||||
"wait":
|
||||
var wait: Dictionary = DMCompiler.extract_mutation("do wait(%s)" % [args.get("value", "null")])
|
||||
mutations.append([index, wait])
|
||||
"speed":
|
||||
speeds[index] = args.get("value").to_float()
|
||||
"/speed":
|
||||
speeds[index] = 1.0
|
||||
"$>", "$>>", "do", "do!", "set":
|
||||
mutations.append([index, args.get("value")])
|
||||
"next":
|
||||
time = args.get("value") if args.has("value") else "0"
|
||||
|
||||
# Find any BB codes that are after this index and remove the length from their start
|
||||
var length: int = bbcode.bbcode.length()
|
||||
for bb: Dictionary in bbcodes:
|
||||
if bb.offset_start > bbcode.start:
|
||||
bb.offset_start -= length
|
||||
bb.start -= length
|
||||
|
||||
# Find any escaped brackets after this that need moving
|
||||
for i: int in range(0, escaped_open_brackets.size()):
|
||||
if escaped_open_brackets[i] > bbcode.start:
|
||||
escaped_open_brackets[i] -= length
|
||||
for i: int in range(0, escaped_close_brackets.size()):
|
||||
if escaped_close_brackets[i] > bbcode.start:
|
||||
escaped_close_brackets[i] -= length
|
||||
|
||||
text = text.substr(0, index) + text.substr(index + length)
|
||||
next_bbcode_position = find_bbcode_positions_in_string(text, false)
|
||||
|
||||
# Put the BB Codes back in
|
||||
for bb: Dictionary in bbcodes:
|
||||
text = text.insert(bb.start, bb.bbcode)
|
||||
|
||||
# Put the escaped brackets back in
|
||||
for index: int in escaped_open_brackets:
|
||||
text = text.left(index) + "[" + text.right(text.length() - index - 1)
|
||||
for index: int in escaped_close_brackets:
|
||||
text = text.left(index) + "]" + text.right(text.length() - index - 1)
|
||||
|
||||
|
||||
static func find_bbcode_positions_in_string(string: String, find_all: bool = true, include_conditions: bool = false) -> Array[Dictionary]:
|
||||
if not "[" in string: return []
|
||||
|
||||
var positions: Array[Dictionary] = []
|
||||
var open_brace_count: int = 0
|
||||
var start: int = 0
|
||||
var bbcode: String = ""
|
||||
var code: String = ""
|
||||
var is_reading_code: bool = false
|
||||
|
||||
var i: int = 0
|
||||
while i < string.length():
|
||||
if string[i] == "[":
|
||||
if open_brace_count == 0:
|
||||
start = i
|
||||
bbcode = ""
|
||||
code = ""
|
||||
is_reading_code = true
|
||||
open_brace_count += 1
|
||||
|
||||
elif is_reading_code:
|
||||
if string[i].to_upper() != string[i] or ["/", "!", "$", ">"].has(string[i]):
|
||||
code += string[i]
|
||||
else:
|
||||
is_reading_code = false
|
||||
|
||||
if open_brace_count > 0:
|
||||
bbcode += string[i]
|
||||
|
||||
if string.substr(i, 2) == "/]":
|
||||
bbcode += "]"
|
||||
i += 1
|
||||
|
||||
if string[i] == "]":
|
||||
open_brace_count -= 1
|
||||
if open_brace_count == 0 and (include_conditions or not ["if", "else", "/if"].has(code)):
|
||||
var raw_args: String = bbcode.substr(code.length() + 1, bbcode.length() - code.length() - 2).strip_edges()
|
||||
if raw_args.ends_with("/"):
|
||||
raw_args = raw_args.substr(0, raw_args.length() - 1)
|
||||
positions.append({
|
||||
bbcode = bbcode,
|
||||
code = code,
|
||||
start = start,
|
||||
end = i,
|
||||
raw_args = raw_args
|
||||
})
|
||||
|
||||
if not find_all:
|
||||
return positions
|
||||
|
||||
i += 1
|
||||
|
||||
return positions
|
||||
@@ -0,0 +1 @@
|
||||
uid://0k6q8kukq0qa
|
||||
@@ -0,0 +1,26 @@
|
||||
## Tag data associated with a line of dialogue.
|
||||
class_name DMResolvedTagData extends RefCounted
|
||||
|
||||
|
||||
## The list of tags.
|
||||
var tags: PackedStringArray = []
|
||||
## The line with any tag syntax removed.
|
||||
var text_without_tags: String = ""
|
||||
|
||||
# An instance of the compiler [RegEx].
|
||||
var regex: DMCompilerRegEx = DMCompilerRegEx.new()
|
||||
|
||||
|
||||
func _init(text: String) -> void:
|
||||
var resolved_tags: PackedStringArray = []
|
||||
var tag_matches: Array[RegExMatch] = regex.TAGS_REGEX.search_all(text)
|
||||
for tag_match: RegExMatch in tag_matches:
|
||||
text = text.replace(tag_match.get_string(), "")
|
||||
var possible_tags: PackedStringArray = tag_match.get_string().replace("[#", "").replace("]", "").replace(", ", ",").split(",")
|
||||
for tag: String in possible_tags:
|
||||
tag = tag.replace("#", "")
|
||||
if not tag in resolved_tags:
|
||||
resolved_tags.append(tag)
|
||||
|
||||
tags = resolved_tags
|
||||
text_without_tags = text
|
||||
@@ -0,0 +1 @@
|
||||
uid://cqai3ikuilqfq
|
||||
@@ -0,0 +1,46 @@
|
||||
## An intermediate representation of a dialogue line before it gets compiled.
|
||||
class_name DMTreeLine extends RefCounted
|
||||
|
||||
|
||||
## The line number where this dialogue was found (after imported files have had their content imported).
|
||||
var line_number: int = 0
|
||||
## The parent [DMTreeLine] of this line.
|
||||
## This is stored as a Weak Reference so that this RefCounted can elegantly free itself.
|
||||
## Without it being a Weak Reference, this can easily cause a cyclical reference that keeps this resource alive.
|
||||
var parent: WeakRef
|
||||
## The ID of this line.
|
||||
var id: String
|
||||
## The type of this line (as a [String] defined in [DMConstants].
|
||||
var type: String = ""
|
||||
## Is this line part of a randomised group?
|
||||
var is_random: bool = false
|
||||
## The indent count for this line.
|
||||
var indent: int = 0
|
||||
## The text of this line.
|
||||
var text: String = ""
|
||||
## The child [DMTreeLine]s of this line.
|
||||
var children: Array[DMTreeLine] = []
|
||||
## Any doc comments attached to this line.
|
||||
var notes: String = ""
|
||||
## Is this a dialogue line that is the child of another dialogue line?
|
||||
var is_nested_dialogue: bool = false
|
||||
|
||||
|
||||
func _init(initial_id: String) -> void:
|
||||
id = initial_id
|
||||
|
||||
|
||||
func _to_string() -> String:
|
||||
var tabs: Array = []
|
||||
tabs.resize(indent)
|
||||
tabs.fill("\t")
|
||||
var tab: String = "".join(tabs)
|
||||
|
||||
return tab.join([tab + "{\n",
|
||||
"\tid: %s\n" % [id],
|
||||
"\ttype: %s\n" % [type],
|
||||
"\tis_random: %s\n" % ["true" if is_random else "false"],
|
||||
"\ttext: %s\n" % [text],
|
||||
"\tnotes: %s\n" % [notes],
|
||||
"\tchildren: []\n" if children.size() == 0 else "\tchildren: [\n" + ",\n".join(children.map(func(child: DMTreeLine) -> String: return str(child))) + "]\n",
|
||||
"}"])
|
||||
@@ -0,0 +1 @@
|
||||
uid://dsu4i84dpif14
|
||||
@@ -0,0 +1,56 @@
|
||||
@tool
|
||||
|
||||
extends CenterContainer
|
||||
|
||||
|
||||
signal new_button_pressed
|
||||
signal quick_open_button_pressed
|
||||
|
||||
|
||||
@onready var banner_image: TextureRect = %BannerImage
|
||||
@onready var new_button: Button = %NewButton
|
||||
@onready var quick_open: Button = %QuickOpen
|
||||
@onready var examples: Button = %Examples
|
||||
@onready var buy_my_game: Button = %BuyMyGame
|
||||
|
||||
|
||||
func apply_theme() -> void:
|
||||
var theme_values: DMThemeValues = DMThemeValues.get_values_from_editor()
|
||||
banner_image.custom_minimum_size = Vector2(300, 200) * theme_values.scale
|
||||
|
||||
new_button.icon = get_theme_icon("New", "EditorIcons")
|
||||
new_button.text = DMConstants.translate(&"start_a_new_file")
|
||||
quick_open.icon = get_theme_icon("Load", "EditorIcons")
|
||||
quick_open.text = DMConstants.translate(&"open.quick_open")
|
||||
|
||||
examples.icon = get_theme_icon("EditorFileDialog", "EditorIcons")
|
||||
examples.text = DMConstants.translate("Get example projects...")
|
||||
buy_my_game.icon = get_theme_icon("CharacterBody2D", "EditorIcons")
|
||||
buy_my_game.text = DMConstants.translate("Buy my game...")
|
||||
|
||||
|
||||
#region Signals
|
||||
|
||||
|
||||
func _on_banner_image_gui_input(event: InputEvent) -> void:
|
||||
if event.is_pressed():
|
||||
OS.shell_open("https://github.com/nathanhoad/godot_dialogue_manager")
|
||||
|
||||
|
||||
func _on_new_button_pressed() -> void:
|
||||
new_button_pressed.emit()
|
||||
|
||||
|
||||
func _on_quick_open_pressed() -> void:
|
||||
quick_open_button_pressed.emit()
|
||||
|
||||
|
||||
func _on_examples_pressed() -> void:
|
||||
OS.shell_open("https://itch.io/c/5226650/godot-dialogue-manager-example-projects")
|
||||
|
||||
|
||||
func _on_buy_my_game_pressed() -> void:
|
||||
OS.shell_open("https://bravestcoconut.com/wishlist")
|
||||
|
||||
|
||||
#endregion
|
||||
@@ -0,0 +1 @@
|
||||
uid://e3wa7g2toi2t
|
||||
@@ -0,0 +1,65 @@
|
||||
[gd_scene format=3 uid="uid://s41qxl30184y"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://e3wa7g2toi2t" path="res://addons/dialogue_manager/components/blank_slate.gd" id="1_l3w2g"]
|
||||
[ext_resource type="Texture2D" uid="uid://cjl44xi6ekrqt" path="res://addons/dialogue_manager/assets/logo.svg" id="2_l3w2g"]
|
||||
|
||||
[node name="BlankSlate" type="CenterContainer" unique_id=267921584]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
script = ExtResource("1_l3w2g")
|
||||
|
||||
[node name="PanelContainer" type="VBoxContainer" parent="." unique_id=1637606585]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 5
|
||||
|
||||
[node name="BannerImage" type="TextureRect" parent="PanelContainer" unique_id=1351355759]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(300, 200)
|
||||
layout_mode = 2
|
||||
mouse_filter = 0
|
||||
mouse_default_cursor_shape = 2
|
||||
texture = ExtResource("2_l3w2g")
|
||||
expand_mode = 1
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="Spacer" type="Control" parent="PanelContainer" unique_id=847267783]
|
||||
custom_minimum_size = Vector2(0, 10)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="NewButton" type="Button" parent="PanelContainer" unique_id=1627079156]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "New Dialogue..."
|
||||
|
||||
[node name="QuickOpen" type="Button" parent="PanelContainer" unique_id=1046857903]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Quick open..."
|
||||
|
||||
[node name="Spacer2" type="Control" parent="PanelContainer" unique_id=1403149639]
|
||||
custom_minimum_size = Vector2(0, 10)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Examples" type="Button" parent="PanelContainer" unique_id=2130533078]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Example projects..."
|
||||
|
||||
[node name="BuyMyGame" type="Button" parent="PanelContainer" unique_id=555819308]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Buy my game..."
|
||||
|
||||
[connection signal="gui_input" from="PanelContainer/BannerImage" to="." method="_on_banner_image_gui_input"]
|
||||
[connection signal="pressed" from="PanelContainer/NewButton" to="." method="_on_new_button_pressed"]
|
||||
[connection signal="pressed" from="PanelContainer/QuickOpen" to="." method="_on_quick_open_pressed"]
|
||||
[connection signal="pressed" from="PanelContainer/Examples" to="." method="_on_examples_pressed"]
|
||||
[connection signal="pressed" from="PanelContainer/BuyMyGame" to="." method="_on_buy_my_game_pressed"]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
uid://djeybvlb332mp
|
||||
@@ -0,0 +1,56 @@
|
||||
[gd_scene format=3 uid="uid://civ6shmka5e8u"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://klpiq4tk3t7a" path="res://addons/dialogue_manager/components/code_edit_syntax_highlighter.gd" id="1_58cfo"]
|
||||
[ext_resource type="Script" uid="uid://djeybvlb332mp" path="res://addons/dialogue_manager/components/code_edit.gd" id="1_g324i"]
|
||||
|
||||
[sub_resource type="SyntaxHighlighter" id="SyntaxHighlighter_crbvo"]
|
||||
script = ExtResource("1_58cfo")
|
||||
|
||||
[node name="CodeEdit" type="CodeEdit" unique_id=236286673]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
text = "~ title_thing
|
||||
|
||||
if this = \"that\" or 'this'
|
||||
Nathan: Something
|
||||
- Then [if test.thing() == 2.0] => somewhere
|
||||
- Other => END!
|
||||
|
||||
~ somewhere
|
||||
|
||||
set has_something = true
|
||||
=> END"
|
||||
scroll_past_end_of_file = true
|
||||
minimap_draw = true
|
||||
syntax_highlighter = SubResource("SyntaxHighlighter_crbvo")
|
||||
highlight_all_occurrences = true
|
||||
highlight_current_line = true
|
||||
draw_tabs = true
|
||||
symbol_lookup_on_click = true
|
||||
line_folding = true
|
||||
gutters_draw_line_numbers = true
|
||||
gutters_draw_fold_gutter = true
|
||||
delimiter_strings = Array[String](["\" \""])
|
||||
delimiter_comments = Array[String](["#"])
|
||||
code_completion_enabled = true
|
||||
code_completion_prefixes = Array[String]([">", "<"])
|
||||
indent_automatic = true
|
||||
auto_brace_completion_enabled = true
|
||||
auto_brace_completion_highlight_matching = true
|
||||
auto_brace_completion_pairs = {
|
||||
"\"": "\"",
|
||||
"(": ")",
|
||||
"[": "]",
|
||||
"{": "}"
|
||||
}
|
||||
script = ExtResource("1_g324i")
|
||||
|
||||
[connection signal="caret_changed" from="." to="." method="_on_code_edit_caret_changed"]
|
||||
[connection signal="gutter_clicked" from="." to="." method="_on_code_edit_gutter_clicked"]
|
||||
[connection signal="symbol_lookup" from="." to="." method="_on_code_edit_symbol_lookup"]
|
||||
[connection signal="symbol_validate" from="." to="." method="_on_code_edit_symbol_validate"]
|
||||
[connection signal="text_changed" from="." to="." method="_on_code_edit_text_changed"]
|
||||
[connection signal="text_set" from="." to="." method="_on_code_edit_text_set"]
|
||||
@@ -0,0 +1,254 @@
|
||||
@tool
|
||||
class_name DMSyntaxHighlighter extends SyntaxHighlighter
|
||||
|
||||
|
||||
var regex: DMCompilerRegEx = DMCompilerRegEx.new()
|
||||
var expression_parser: DMExpressionParser = DMExpressionParser.new()
|
||||
|
||||
var cache: Dictionary = {}
|
||||
|
||||
|
||||
func _clear_highlighting_cache() -> void:
|
||||
cache.clear()
|
||||
|
||||
|
||||
func _get_line_syntax_highlighting(line: int) -> Dictionary:
|
||||
expression_parser.include_comments = true
|
||||
|
||||
var colors: Dictionary = {}
|
||||
var text_edit: TextEdit = get_text_edit()
|
||||
var text: String = text_edit.get_line(line)
|
||||
|
||||
# Prevent an error from popping up while developing
|
||||
if not is_instance_valid(text_edit):
|
||||
return colors
|
||||
|
||||
# Disable this, as well as the line at the bottom of this function to remove the cache.
|
||||
if text in cache:
|
||||
return cache[text]
|
||||
|
||||
var theme: DMThemeValues = text_edit.theme_overrides
|
||||
|
||||
var index: int = 0
|
||||
|
||||
match DMCompiler.get_line_type(text):
|
||||
DMConstants.TYPE_USING:
|
||||
colors[index] = { color = theme.conditions_color }
|
||||
colors[index + "using ".length()] = { color = theme.text_color }
|
||||
|
||||
DMConstants.TYPE_IMPORT:
|
||||
colors[index] = { color = theme.conditions_color }
|
||||
var import: RegExMatch = regex.IMPORT_REGEX.search(text)
|
||||
if import:
|
||||
colors[index + import.get_start("path") - 1] = { color = theme.strings_color }
|
||||
colors[index + import.get_end("path") + 1] = { color = theme.conditions_color }
|
||||
colors[index + import.get_start("prefix")] = { color = theme.text_color }
|
||||
|
||||
DMConstants.TYPE_COMMENT:
|
||||
colors[index] = { color = theme.comments_color }
|
||||
|
||||
DMConstants.TYPE_CUE:
|
||||
colors[index] = { color = theme.cues_color }
|
||||
|
||||
DMConstants.TYPE_CONDITION, DMConstants.TYPE_WHILE, DMConstants.TYPE_MATCH, DMConstants.TYPE_WHEN:
|
||||
colors[0] = { color = theme.conditions_color }
|
||||
index = text.find(" ")
|
||||
if index > -1:
|
||||
var expression: Array = expression_parser.tokenise(text.substr(index), DMConstants.TYPE_CONDITION, 0)
|
||||
if expression.size() == 0:
|
||||
colors[index] = { color = theme.critical_color }
|
||||
else:
|
||||
_highlight_expression(expression, colors, index)
|
||||
|
||||
DMConstants.TYPE_MUTATION:
|
||||
colors[0] = { color = theme.mutations_line_color }
|
||||
index = text.find(" ")
|
||||
var expression: Array = expression_parser.tokenise(text.substr(index), DMConstants.TYPE_MUTATION, 0)
|
||||
if expression.size() == 0:
|
||||
colors[index] = { color = theme.critical_color }
|
||||
else:
|
||||
_highlight_expression(expression, colors, index)
|
||||
|
||||
DMConstants.TYPE_GOTO:
|
||||
if text.strip_edges().begins_with("%"):
|
||||
colors[index] = { color = theme.numbers_color }
|
||||
index = text.find(" ")
|
||||
_highlight_goto(text, colors, index)
|
||||
|
||||
DMConstants.TYPE_RANDOM:
|
||||
colors[index] = { color = theme.numbers_color }
|
||||
|
||||
DMConstants.TYPE_DIALOGUE, DMConstants.TYPE_RESPONSE:
|
||||
if text.strip_edges().begins_with("- "):
|
||||
colors[index] = { color = theme.cues_color }
|
||||
index = text.find(" ", text.find("- "))
|
||||
|
||||
if text.strip_edges().begins_with("%"):
|
||||
colors[index] = { color = theme.numbers_color }
|
||||
index = text.find(" ", text.find("%"))
|
||||
colors[index] = { color = theme.text_color.lerp(theme.symbols_color, 0.5) }
|
||||
|
||||
var dialogue_text: String = text.substr(index, text.find("=>"))
|
||||
|
||||
# Highlight character name (but ignore ":" within line ID reference)
|
||||
var split_index: int = dialogue_text.replace("\\:", "??").find(":")
|
||||
if dialogue_text.substr(split_index - 3, 3) != "[ID":
|
||||
colors[index + split_index + 1] = { color = theme.text_color }
|
||||
else:
|
||||
# If there's no character name then just highlight the text as dialogue.
|
||||
colors[index] = { color = theme.text_color }
|
||||
|
||||
# Interpolation
|
||||
var replacements: Array[RegExMatch] = regex.REPLACEMENTS_REGEX.search_all(dialogue_text)
|
||||
for replacement: RegExMatch in replacements:
|
||||
var expression_text: String = replacement.get_string().substr(0, replacement.get_string().length() - 2).substr(2)
|
||||
var expression: Array = expression_parser.tokenise(expression_text, DMConstants.TYPE_MUTATION, replacement.get_start())
|
||||
var expression_index: int = index + replacement.get_start()
|
||||
colors[expression_index] = { color = theme.symbols_color }
|
||||
if expression.size() == 0 or expression[0].type == DMConstants.TYPE_ERROR:
|
||||
colors[expression_index] = { color = theme.critical_color }
|
||||
else:
|
||||
_highlight_expression(expression, colors, index + 2)
|
||||
colors[expression_index + expression_text.length() + 2] = { color = theme.symbols_color }
|
||||
colors[expression_index + expression_text.length() + 4] = { color = theme.text_color }
|
||||
# Tags (and inline mutations)
|
||||
var bbcodes: Array[Dictionary] = DMResolvedLineData.find_bbcode_positions_in_string(dialogue_text, true, true)
|
||||
for bbcode: Dictionary in bbcodes:
|
||||
var tag: String = bbcode.code
|
||||
var code: String = bbcode.raw_args
|
||||
if code.begins_with("["):
|
||||
colors[index + bbcode.start] = { color = theme.tags_color }
|
||||
colors[index + bbcode.start + 2] = { color = theme.text_color }
|
||||
var pipe_cursor: int = code.find("|")
|
||||
while pipe_cursor > -1:
|
||||
colors[index + bbcode.start + pipe_cursor + 1] = { color = theme.tags_color }
|
||||
colors[index + bbcode.start + pipe_cursor + 2] = { color = theme.text_color }
|
||||
pipe_cursor = code.find("|", pipe_cursor + 1)
|
||||
colors[index + bbcode.end - 1] = { color = theme.tags_color }
|
||||
colors[index + bbcode.end + 1] = { color = theme.text_color }
|
||||
else:
|
||||
colors[index + bbcode.start] = { color = theme.tags_color }
|
||||
colors[index + bbcode.end] = { color = theme.tags_color }
|
||||
if bbcode.bbcode.ends_with(" /]"):
|
||||
colors[index + bbcode.end - 1] = { color = theme.tags_color }
|
||||
colors[index + bbcode.end + 1] = { color = theme.text_color }
|
||||
else:
|
||||
colors[index + bbcode.end + 1] = { color = theme.text_color }
|
||||
|
||||
if tag.begins_with("$>") or tag.begins_with("do") or tag.begins_with("set") or tag.begins_with("if"):
|
||||
if tag.begins_with("if"):
|
||||
colors[index + bbcode.start] = { color = theme.conditions_color }
|
||||
if bbcode.bbcode.ends_with(" /]"):
|
||||
colors[index + bbcode.end - 1] = { color = theme.conditions_color }
|
||||
colors[index + bbcode.end] = { color = theme.conditions_color }
|
||||
colors[index + bbcode.end + 1] = { color = theme.conditions_color }
|
||||
colors[index + bbcode.end + 1] = { color = theme.text_color }
|
||||
else:
|
||||
colors[index + bbcode.end] = { color = theme.conditions_color }
|
||||
else:
|
||||
colors[index + bbcode.start] = { color = theme.mutations_line_color }
|
||||
colors[index + bbcode.end] = { color = theme.mutations_line_color }
|
||||
var expression: Array = expression_parser.tokenise(code, DMConstants.TYPE_MUTATION, bbcode.start + bbcode.code.length())
|
||||
if expression.size() == 0 or expression[0].type == DMConstants.TYPE_ERROR:
|
||||
colors[index + bbcode.start + tag.length() + 1] = { color = theme.critical_color }
|
||||
else:
|
||||
_highlight_expression(expression, colors, index + 2)
|
||||
# else and closing if have no expression
|
||||
elif tag.begins_with("else") or tag.begins_with("/if"):
|
||||
colors[index + bbcode.start] = { color = theme.conditions_color }
|
||||
colors[index + bbcode.end] = { color = theme.conditions_color }
|
||||
|
||||
# Jumps
|
||||
if "=> " in text or "=>< " in text:
|
||||
_highlight_goto(text, colors, index)
|
||||
|
||||
# Order the dictionary keys to prevent CodeEdit from having issues
|
||||
var ordered_colors: Dictionary = {}
|
||||
var ordered_keys: Array = colors.keys()
|
||||
ordered_keys.sort()
|
||||
for key_index: int in ordered_keys:
|
||||
ordered_colors[key_index] = colors[key_index]
|
||||
|
||||
cache[text] = ordered_colors
|
||||
return ordered_colors
|
||||
|
||||
|
||||
func _highlight_expression(tokens: Array, colors: Dictionary, index: int) -> int:
|
||||
var theme: DMThemeValues = get_text_edit().theme_overrides
|
||||
var last_index: int = index
|
||||
for token: Dictionary in tokens:
|
||||
last_index = token.i
|
||||
match token.type:
|
||||
DMConstants.TOKEN_COMMENT:
|
||||
colors[index + token.i] = { color = theme.comments_color }
|
||||
|
||||
DMConstants.TOKEN_CONDITION, DMConstants.TOKEN_AND_OR, DMConstants.TOKEN_NOT:
|
||||
colors[index + token.i] = { color = theme.conditions_color }
|
||||
|
||||
DMConstants.TOKEN_VARIABLE:
|
||||
if token.value in ["true", "false"]:
|
||||
colors[index + token.i] = { color = theme.conditions_color }
|
||||
else:
|
||||
colors[index + token.i] = { color = theme.members_color }
|
||||
|
||||
DMConstants.TOKEN_OPERATOR, DMConstants.TOKEN_COLON, \
|
||||
DMConstants.TOKEN_COMMA, DMConstants.TOKEN_DOT, DMConstants.TOKEN_NULL_COALESCE, \
|
||||
DMConstants.TOKEN_ASSIGNMENT, DMConstants.TOKEN_COMPARISON:
|
||||
if token.get("value", null) == "in":
|
||||
colors[index + token.i] = { color = theme.conditions_color }
|
||||
else:
|
||||
colors[index + token.i] = { color = theme.symbols_color }
|
||||
|
||||
DMConstants.TOKEN_NUMBER:
|
||||
colors[index + token.i] = { color = theme.numbers_color }
|
||||
|
||||
DMConstants.TOKEN_STRING:
|
||||
colors[index + token.i] = { color = theme.strings_color }
|
||||
|
||||
DMConstants.TOKEN_FUNCTION:
|
||||
colors[index + token.i] = { color = theme.mutations_color }
|
||||
colors[index + token.i + token.function.length()] = { color = theme.symbols_color }
|
||||
for parameter: Array in token.value:
|
||||
last_index = _highlight_expression(parameter, colors, index)
|
||||
DMConstants.TOKEN_PARENS_CLOSE:
|
||||
colors[index + token.i] = { color = theme.symbols_color }
|
||||
|
||||
DMConstants.TOKEN_DICTIONARY_REFERENCE:
|
||||
colors[index + token.i] = { color = theme.members_color }
|
||||
colors[index + token.i + token.variable.length()] = { color = theme.symbols_color }
|
||||
last_index = _highlight_expression(token.value, colors, index)
|
||||
DMConstants.TOKEN_ARRAY:
|
||||
colors[index + token.i] = { color = theme.symbols_color }
|
||||
for item: Array in token.value:
|
||||
last_index = _highlight_expression(item, colors, index)
|
||||
DMConstants.TOKEN_BRACKET_CLOSE:
|
||||
colors[index + token.i] = { color = theme.symbols_color }
|
||||
|
||||
DMConstants.TOKEN_DICTIONARY:
|
||||
colors[index + token.i] = { color = theme.symbols_color }
|
||||
last_index = _highlight_expression(token.value.keys() + token.value.values(), colors, index)
|
||||
DMConstants.TOKEN_BRACE_CLOSE:
|
||||
colors[index + token.i] = { color = theme.symbols_color }
|
||||
last_index += 1
|
||||
|
||||
DMConstants.TOKEN_GROUP:
|
||||
last_index = _highlight_expression(token.value, colors, index)
|
||||
|
||||
return last_index
|
||||
|
||||
|
||||
func _highlight_goto(text: String, colors: Dictionary, index: int) -> int:
|
||||
var theme: DMThemeValues = get_text_edit().theme_overrides
|
||||
var goto_data: DMResolvedGotoData = DMResolvedGotoData.new(text, {})
|
||||
colors[goto_data.index] = { color = theme.jumps_color }
|
||||
if "{{" in text:
|
||||
index = text.find("{{", goto_data.index)
|
||||
var last_index: int = 0
|
||||
if goto_data.error:
|
||||
colors[index + 2] = { color = theme.critical_color }
|
||||
else:
|
||||
last_index = _highlight_expression(goto_data.expression, colors, index)
|
||||
index = text.find("}}", index + last_index)
|
||||
colors[index] = { color = theme.jumps_color }
|
||||
|
||||
return index
|
||||
@@ -0,0 +1 @@
|
||||
uid://klpiq4tk3t7a
|
||||
@@ -0,0 +1,43 @@
|
||||
@tool
|
||||
class_name DMCueEditorProperty extends EditorProperty
|
||||
|
||||
|
||||
var DMCuePropertyEditorControl: PackedScene = preload("./editor_property_control.tscn")
|
||||
var control: Control = DMCuePropertyEditorControl.instantiate()
|
||||
var current_value: String
|
||||
var is_updating: bool = false
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
add_child(control)
|
||||
|
||||
control.cue = current_value
|
||||
|
||||
control.cue_changed.connect(_on_cue_changed)
|
||||
|
||||
|
||||
func _update_property() -> void:
|
||||
control.actionable = get_edited_object()
|
||||
|
||||
var next_value: String = get_edited_object()[get_edited_property()]
|
||||
|
||||
if next_value.is_empty():
|
||||
control.cue = ""
|
||||
return
|
||||
|
||||
if next_value == current_value: return
|
||||
|
||||
is_updating = true
|
||||
current_value = next_value
|
||||
control.cue = current_value
|
||||
is_updating = false
|
||||
|
||||
|
||||
#region Signals
|
||||
|
||||
|
||||
func _on_cue_changed(next_cue: String) -> void:
|
||||
emit_changed(get_edited_property(), next_cue)
|
||||
|
||||
|
||||
#endregion
|
||||
@@ -0,0 +1 @@
|
||||
uid://bak7go3j7n0qf
|
||||
@@ -0,0 +1,142 @@
|
||||
@tool
|
||||
|
||||
extends HBoxContainer
|
||||
|
||||
|
||||
signal cue_changed(next_cue: String)
|
||||
|
||||
|
||||
const CUE_ICON: Texture2D = preload("uid://d1aawtj2vsnxx")
|
||||
|
||||
|
||||
var actionable: Node = null
|
||||
var cue: String = "":
|
||||
set(value):
|
||||
cue = value
|
||||
if not is_node_ready():
|
||||
await ready
|
||||
_update()
|
||||
get:
|
||||
return cue
|
||||
|
||||
var link_action: String = "show"
|
||||
|
||||
@onready var button: OptionButton = %Button
|
||||
@onready var link_button: Button = %LinkButton
|
||||
@onready var confirm_cue_name_dialog: ConfirmationDialog = %ConfirmCueNameDialog
|
||||
@onready var cue_name_label: Label = %CueNameLabel
|
||||
@onready var cue_name_edit: LineEdit = %CueNameEdit
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
confirm_cue_name_dialog.register_text_enter(cue_name_edit)
|
||||
|
||||
_update()
|
||||
|
||||
button.get_popup().index_pressed.connect(_on_index_pressed)
|
||||
button.get_popup().about_to_popup.connect(_on_menu_about_to_popup)
|
||||
|
||||
DMPlugin.instance.import_plugin.compiled_resource.connect(_on_dialogue_compiled)
|
||||
|
||||
|
||||
func _update() -> void:
|
||||
var resource: DialogueResource = _get_resource()
|
||||
|
||||
var cues: PackedStringArray = []
|
||||
if is_instance_valid(resource):
|
||||
cues = Array(resource.get_cues()).filter(func(l: String) -> bool: return not l.contains("/"))
|
||||
|
||||
link_button.disabled = not is_instance_valid(resource)
|
||||
|
||||
var popup: PopupMenu = button.get_popup()
|
||||
popup.clear()
|
||||
|
||||
var cue_icon: Texture2D = DMThemeValues.get_icon_with_color(CUE_ICON, DMThemeValues.get_values_from_editor().cues_color)
|
||||
|
||||
if cues.is_empty() or not is_instance_valid(resource):
|
||||
popup.add_item(DMConstants.translate("<empty>"))
|
||||
popup.set_item_disabled(0, true)
|
||||
else:
|
||||
for existing_cue: String in cues:
|
||||
popup.add_icon_item(cue_icon, existing_cue)
|
||||
if not cue.is_empty() and not cues.has(cue):
|
||||
popup.add_icon_item(CUE_ICON, cue)
|
||||
|
||||
if cue.is_empty():
|
||||
button.text = DMConstants.translate("<empty>")
|
||||
button.icon = null
|
||||
link_button.icon = get_theme_icon("New", "EditorIcons")
|
||||
link_action = "create"
|
||||
elif cues.has(cue):
|
||||
button.select(-1)
|
||||
button.select(cues.find(cue))
|
||||
link_button.icon = get_theme_icon("ExternalLink", "EditorIcons")
|
||||
link_action = "show"
|
||||
else:
|
||||
button.select(cues.size())
|
||||
link_button.icon = get_theme_icon("New", "EditorIcons")
|
||||
link_action = "create"
|
||||
|
||||
|
||||
func show_cue_in_editor(next_cue: String) -> void:
|
||||
var resource: DialogueResource = _get_resource()
|
||||
if is_instance_valid(resource):
|
||||
DMPlugin.open_file_at_cue(resource, next_cue, true)
|
||||
|
||||
|
||||
func _get_resource() -> DialogueResource:
|
||||
if not is_instance_valid(actionable): return null
|
||||
|
||||
var resource: DialogueResource
|
||||
for property: Dictionary in actionable.get_property_list():
|
||||
if "dialogue_resource" in property.name or "DialogueResource" in property.name or (property.class_name == "Resource" and actionable.get(property.name) is DialogueResource):
|
||||
resource = actionable.get(property.name)
|
||||
|
||||
if is_instance_valid(resource):
|
||||
return ResourceLoader.load(resource.resource_path, "", ResourceLoader.CACHE_MODE_REPLACE)
|
||||
|
||||
return null
|
||||
|
||||
|
||||
#region Signals
|
||||
|
||||
|
||||
func _on_dialogue_compiled(_resource: DialogueResource) -> void:
|
||||
_update.call_deferred()
|
||||
|
||||
|
||||
func _on_menu_about_to_popup() -> void:
|
||||
_update()
|
||||
|
||||
|
||||
func _on_index_pressed(index: int) -> void:
|
||||
cue = button.get_popup().get_item_text(index)
|
||||
cue_changed.emit(cue)
|
||||
_update()
|
||||
|
||||
|
||||
func _on_link_button_pressed() -> void:
|
||||
match link_action:
|
||||
"create":
|
||||
if cue.is_empty():
|
||||
cue_name_edit.text = actionable.name.to_snake_case()
|
||||
else:
|
||||
cue_name_edit.text = cue
|
||||
cue_name_label.text = DMConstants.translate("Cue:")
|
||||
confirm_cue_name_dialog.popup_centered()
|
||||
cue_name_edit.grab_focus.call_deferred()
|
||||
cue_name_edit.select_all.call_deferred()
|
||||
|
||||
"show":
|
||||
show_cue_in_editor.call_deferred(cue)
|
||||
_update.call_deferred()
|
||||
|
||||
|
||||
func _on_confirm_cue_name_dialog_confirmed() -> void:
|
||||
cue = cue_name_edit.text.to_snake_case()
|
||||
cue_changed.emit(cue)
|
||||
show_cue_in_editor.call_deferred(cue)
|
||||
_update.call_deferred()
|
||||
|
||||
|
||||
#endregion
|
||||
@@ -0,0 +1 @@
|
||||
uid://cn5hcy57m088v
|
||||
@@ -0,0 +1,49 @@
|
||||
[gd_scene format=3 uid="uid://molgra7vqxts"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cn5hcy57m088v" path="res://addons/dialogue_manager/components/cue_editor_property/editor_property_control.gd" id="1_4prea"]
|
||||
|
||||
[node name="EditorPropertyControl" type="HBoxContainer" unique_id=1721562088]
|
||||
offset_right = 40.0
|
||||
offset_bottom = 40.0
|
||||
script = ExtResource("1_4prea")
|
||||
|
||||
[node name="Button" type="OptionButton" parent="." unique_id=1093477450]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
flat = true
|
||||
alignment = 1
|
||||
|
||||
[node name="LinkButton" type="Button" parent="." unique_id=713900927]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
flat = true
|
||||
|
||||
[node name="ConfirmCueNameDialog" type="ConfirmationDialog" parent="." unique_id=967805543]
|
||||
unique_name_in_owner = true
|
||||
oversampling_override = 1.0
|
||||
size = Vector2i(200, 115)
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="ConfirmCueNameDialog" unique_id=151160442]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 8.0
|
||||
offset_top = 8.0
|
||||
offset_right = -8.0
|
||||
offset_bottom = -49.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="CueNameLabel" type="Label" parent="ConfirmCueNameDialog/VBoxContainer" unique_id=1635294933]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Cue:"
|
||||
|
||||
[node name="CueNameEdit" type="LineEdit" parent="ConfirmCueNameDialog/VBoxContainer" unique_id=830071500]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[connection signal="pressed" from="LinkButton" to="." method="_on_link_button_pressed"]
|
||||
[connection signal="confirmed" from="ConfirmCueNameDialog" to="." method="_on_confirm_cue_name_dialog_confirmed"]
|
||||
@@ -0,0 +1,131 @@
|
||||
@tool
|
||||
extends VBoxContainer
|
||||
|
||||
signal cue_selected(cue: String)
|
||||
|
||||
|
||||
const REGION_ICON: Texture2D = preload("../assets/region.svg")
|
||||
const CUE_ICON: Texture2D = preload("../assets/cue.svg")
|
||||
|
||||
const VIEW_REGIONS_CUES: String = "regions+cues"
|
||||
const VIEW_REGIONS: String = "regions"
|
||||
const VIEW_CUES: String = "cues"
|
||||
const VIEW_AUTO: String = "auto"
|
||||
|
||||
|
||||
var cues: PackedStringArray:
|
||||
set(value):
|
||||
cues = value
|
||||
apply_filter()
|
||||
get:
|
||||
return cues
|
||||
|
||||
var filter: String:
|
||||
set(next_filter):
|
||||
filter = next_filter
|
||||
apply_filter()
|
||||
get:
|
||||
return filter
|
||||
|
||||
@onready var filter_edit: LineEdit = %FilterEdit
|
||||
@onready var menu_button: MenuButton = %MenuButton
|
||||
@onready var list: ItemList = %List
|
||||
|
||||
var region_icon: Texture2D
|
||||
var cue_icon: Texture2D
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
apply_theme()
|
||||
|
||||
filter_edit.placeholder_text = DMConstants.translate(&"cues_list.filter")
|
||||
|
||||
menu_button.get_popup().id_pressed.connect(_on_menu_button_id_pressed)
|
||||
|
||||
|
||||
func select_cue(cue: String) -> void:
|
||||
list.deselect_all()
|
||||
for i: int in range(0, list.get_item_count()):
|
||||
if list.get_item_text(i) == cue.strip_edges():
|
||||
list.select(i)
|
||||
|
||||
|
||||
func apply_filter() -> void:
|
||||
list.clear()
|
||||
|
||||
var view_setting: String = DMSettings.get_user_value("cue_list_view", "regions+cues")
|
||||
# If auto then see if there are regions in the list to then default to regions but if there
|
||||
# are none default to cues instead
|
||||
if view_setting == VIEW_AUTO:
|
||||
view_setting = VIEW_CUES
|
||||
for cue: String in cues:
|
||||
if cue.begins_with("#"):
|
||||
view_setting = VIEW_REGIONS
|
||||
break
|
||||
|
||||
for cue: String in cues:
|
||||
if filter == "" or filter.to_lower() in cue.to_lower():
|
||||
if cue.begins_with("#"):
|
||||
if VIEW_REGIONS in view_setting:
|
||||
list.add_item(cue.strip_edges().substr(1), region_icon)
|
||||
elif VIEW_CUES in view_setting:
|
||||
list.add_item(cue.strip_edges(), cue_icon)
|
||||
|
||||
|
||||
func apply_theme() -> void:
|
||||
if is_instance_valid(filter_edit):
|
||||
filter_edit.right_icon = get_theme_icon("Search", "EditorIcons")
|
||||
if is_instance_valid(menu_button):
|
||||
menu_button.icon = get_theme_icon("GuiTabMenu", "EditorIcons")
|
||||
if is_instance_valid(list):
|
||||
list.add_theme_stylebox_override("panel", get_theme_stylebox("panel", "Panel"))
|
||||
|
||||
var theme_values: DMThemeValues = DMThemeValues.get_values_from_editor()
|
||||
|
||||
region_icon = DMThemeValues.get_icon_with_color(REGION_ICON, theme_values.comments_color)
|
||||
cue_icon = DMThemeValues.get_icon_with_color(CUE_ICON, theme_values.cues_color)
|
||||
|
||||
|
||||
#region Signals
|
||||
|
||||
|
||||
func _on_theme_changed() -> void:
|
||||
apply_theme()
|
||||
|
||||
|
||||
func _on_filter_edit_text_changed(new_text: String) -> void:
|
||||
self.filter = new_text
|
||||
|
||||
|
||||
func _on_list_item_clicked(index: int, _at_position: Vector2, mouse_button_index: int) -> void:
|
||||
if mouse_button_index == MOUSE_BUTTON_LEFT:
|
||||
var cue: String = list.get_item_text(index)
|
||||
cue_selected.emit(cue)
|
||||
|
||||
|
||||
func _on_menu_button_about_to_popup() -> void:
|
||||
var menu: PopupMenu = menu_button.get_popup()
|
||||
menu.clear()
|
||||
menu.add_radio_check_item(DMConstants.translate(&"Regions & Cues"), 0)
|
||||
menu.set_item_checked(0, DMSettings.get_user_value("cue_list_view", VIEW_REGIONS_CUES) == VIEW_REGIONS_CUES)
|
||||
menu.add_radio_check_item(DMConstants.translate(&"Regions"), 1)
|
||||
menu.set_item_checked(1, DMSettings.get_user_value("cue_list_view", VIEW_REGIONS_CUES) == VIEW_REGIONS)
|
||||
menu.add_radio_check_item(DMConstants.translate(&"Cues"), 2)
|
||||
menu.set_item_checked(2, DMSettings.get_user_value("cue_list_view", VIEW_REGIONS_CUES) == VIEW_CUES)
|
||||
menu.add_radio_check_item(DMConstants.translate(&"Auto"), 3)
|
||||
menu.set_item_checked(3, DMSettings.get_user_value("cue_list_view", VIEW_REGIONS_CUES) == VIEW_AUTO)
|
||||
|
||||
|
||||
func _on_menu_button_id_pressed(id: int) -> void:
|
||||
match id:
|
||||
0:
|
||||
DMSettings.set_user_value("cue_list_view", VIEW_REGIONS_CUES)
|
||||
1:
|
||||
DMSettings.set_user_value("cue_list_view", VIEW_REGIONS)
|
||||
2:
|
||||
DMSettings.set_user_value("cue_list_view", VIEW_CUES)
|
||||
3:
|
||||
DMSettings.set_user_value("cue_list_view", VIEW_AUTO)
|
||||
apply_filter()
|
||||
|
||||
#endregion
|
||||
@@ -0,0 +1 @@
|
||||
uid://d0k2wndjj0ifm
|
||||
@@ -0,0 +1,38 @@
|
||||
[gd_scene format=3 uid="uid://ctns6ouwwd68i"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://d0k2wndjj0ifm" path="res://addons/dialogue_manager/components/cue_list.gd" id="1_5qqmd"]
|
||||
|
||||
[node name="CueList" type="VBoxContainer" unique_id=1743153913]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
script = ExtResource("1_5qqmd")
|
||||
|
||||
[node name="Toolbar" type="HBoxContainer" parent="." unique_id=1431748116]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="FilterEdit" type="LineEdit" parent="Toolbar" unique_id=1569476716]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
placeholder_text = "Filter cues"
|
||||
clear_button_enabled = true
|
||||
|
||||
[node name="MenuButton" type="MenuButton" parent="Toolbar" unique_id=118120877]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="List" type="ItemList" parent="." unique_id=647368384]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
allow_reselect = true
|
||||
|
||||
[connection signal="theme_changed" from="." to="." method="_on_theme_changed"]
|
||||
[connection signal="text_changed" from="Toolbar/FilterEdit" to="." method="_on_filter_edit_text_changed"]
|
||||
[connection signal="about_to_popup" from="Toolbar/MenuButton" to="." method="_on_menu_button_about_to_popup"]
|
||||
[connection signal="item_clicked" from="List" to="." method="_on_list_item_clicked"]
|
||||
@@ -0,0 +1,42 @@
|
||||
@tool
|
||||
|
||||
class_name DMDialogueEditorProperty extends EditorProperty
|
||||
|
||||
|
||||
const DialoguePropertyEditorControl: PackedScene = preload("./editor_property_control.tscn")
|
||||
|
||||
|
||||
var control: Control = DialoguePropertyEditorControl.instantiate()
|
||||
var current_value: DialogueResource
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
add_child(control)
|
||||
|
||||
control.resource = current_value
|
||||
|
||||
control.resource_changed.connect(_on_resource_changed)
|
||||
|
||||
|
||||
func _update_property() -> void:
|
||||
var next_value: DialogueResource = get_edited_object()[get_edited_property()]
|
||||
|
||||
# The resource might have been deleted elsewhere so check that it's not in a weird state
|
||||
if is_instance_valid(next_value) and not next_value.resource_path.ends_with(".dialogue"):
|
||||
emit_changed(get_edited_property(), null)
|
||||
return
|
||||
|
||||
if next_value == current_value: return
|
||||
|
||||
current_value = next_value
|
||||
control.resource = current_value
|
||||
|
||||
|
||||
#region Signals
|
||||
|
||||
|
||||
func _on_resource_changed(next_resource: DialogueResource) -> void:
|
||||
emit_changed(get_edited_property(), next_resource)
|
||||
|
||||
|
||||
#endregion
|
||||
@@ -0,0 +1 @@
|
||||
uid://nyypeje1a036
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
@tool
|
||||
|
||||
extends HBoxContainer
|
||||
|
||||
|
||||
signal resource_changed(next_resource: DialogueResource)
|
||||
|
||||
|
||||
const ITEM_NEW: int = 100
|
||||
const ITEM_QUICK_LOAD: int = 200
|
||||
const ITEM_LOAD: int = 201
|
||||
const ITEM_EDIT: int = 300
|
||||
const ITEM_CLEAR: int = 301
|
||||
const ITEM_FILESYSTEM: int = 400
|
||||
|
||||
|
||||
var resource: Resource:
|
||||
set(next_resource):
|
||||
resource = next_resource
|
||||
if button:
|
||||
button.resource = resource
|
||||
get:
|
||||
return resource
|
||||
|
||||
var quick_selected_file: String = ""
|
||||
|
||||
@onready var button: Button = $ResourceButton
|
||||
@onready var menu_button: Button = $MenuButton
|
||||
@onready var menu: PopupMenu = $Menu
|
||||
@onready var quick_open_dialog: ConfirmationDialog = $QuickOpenDialog
|
||||
@onready var files_list: Control = $QuickOpenDialog/FilesList
|
||||
@onready var new_dialog: FileDialog = $NewDialog
|
||||
@onready var open_dialog: FileDialog = $OpenDialog
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
menu_button.icon = get_theme_icon("GuiDropdown", "EditorIcons")
|
||||
|
||||
|
||||
func build_menu() -> void:
|
||||
menu.clear()
|
||||
|
||||
menu.add_icon_item(DMPlugin.instance._get_plugin_icon(), "New Dialogue", ITEM_NEW)
|
||||
menu.add_separator()
|
||||
menu.add_icon_item(get_theme_icon("Load", "EditorIcons"), "Quick Load", ITEM_QUICK_LOAD)
|
||||
menu.add_icon_item(get_theme_icon("Load", "EditorIcons"), "Load", ITEM_LOAD)
|
||||
if resource:
|
||||
menu.add_icon_item(get_theme_icon("Edit", "EditorIcons"), "Edit", ITEM_EDIT)
|
||||
menu.add_icon_item(get_theme_icon("Clear", "EditorIcons"), "Clear", ITEM_CLEAR)
|
||||
menu.add_separator()
|
||||
menu.add_item("Show in FileSystem", ITEM_FILESYSTEM)
|
||||
|
||||
menu.size = Vector2.ZERO
|
||||
|
||||
|
||||
#region Signals
|
||||
|
||||
|
||||
func _on_new_dialog_file_selected(path: String) -> void:
|
||||
DMPlugin.instance.main_view.new_file(path)
|
||||
if DMCache.has_file(path):
|
||||
resource_changed.emit(load(path))
|
||||
else:
|
||||
var next_resource: DialogueResource = await DMPlugin.instance.import_plugin.compiled_resource
|
||||
next_resource.resource_path = path
|
||||
resource_changed.emit(next_resource)
|
||||
|
||||
|
||||
func _on_open_dialog_file_selected(file: String) -> void:
|
||||
resource_changed.emit(load(file))
|
||||
|
||||
|
||||
func _on_resource_button_pressed() -> void:
|
||||
if is_instance_valid(resource):
|
||||
DMPlugin.open_file(resource)
|
||||
|
||||
elif menu.visible:
|
||||
menu.hide()
|
||||
else:
|
||||
build_menu()
|
||||
@warning_ignore("narrowing_conversion")
|
||||
menu.position = get_viewport().position + Vector2i(
|
||||
button.global_position.x + button.size.x - menu.size.x,
|
||||
2 + menu_button.global_position.y + button.size.y
|
||||
)
|
||||
menu.popup()
|
||||
|
||||
|
||||
func _on_resource_button_resource_dropped(next_resource: Resource) -> void:
|
||||
resource_changed.emit(next_resource)
|
||||
|
||||
|
||||
func _on_menu_button_pressed() -> void:
|
||||
if menu.visible:
|
||||
menu.hide()
|
||||
else:
|
||||
build_menu()
|
||||
@warning_ignore("narrowing_conversion")
|
||||
menu.position = get_viewport().position + Vector2i(
|
||||
menu_button.global_position.x + menu_button.size.x - menu.size.x,
|
||||
2 + menu_button.global_position.y + menu_button.size.y
|
||||
)
|
||||
menu.popup()
|
||||
|
||||
|
||||
func _on_menu_id_pressed(id: int) -> void:
|
||||
match id:
|
||||
ITEM_NEW:
|
||||
new_dialog.popup_centered()
|
||||
|
||||
ITEM_QUICK_LOAD:
|
||||
quick_selected_file = ""
|
||||
files_list.files = DMCache.get_files()
|
||||
if resource:
|
||||
files_list.select_file(resource.resource_path)
|
||||
quick_open_dialog.popup_centered()
|
||||
files_list.focus_filter()
|
||||
|
||||
ITEM_LOAD:
|
||||
open_dialog.popup_centered()
|
||||
|
||||
ITEM_EDIT:
|
||||
DMPlugin.open_file(resource)
|
||||
|
||||
ITEM_CLEAR:
|
||||
resource_changed.emit(null)
|
||||
|
||||
ITEM_FILESYSTEM:
|
||||
EditorInterface.get_file_system_dock().navigate_to_path(resource.resource_path)
|
||||
|
||||
|
||||
func _on_files_list_file_double_clicked(file_path: String) -> void:
|
||||
resource_changed.emit(load(file_path))
|
||||
quick_open_dialog.hide()
|
||||
|
||||
|
||||
func _on_files_list_file_selected(file_path: String) -> void:
|
||||
quick_selected_file = file_path
|
||||
|
||||
|
||||
func _on_quick_open_dialog_confirmed() -> void:
|
||||
if quick_selected_file != "":
|
||||
resource_changed.emit(load(quick_selected_file))
|
||||
|
||||
|
||||
#endregion
|
||||
+1
@@ -0,0 +1 @@
|
||||
uid://dooe2pflnqtve
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
[gd_scene format=3 uid="uid://ycn6uaj7dsrh"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dooe2pflnqtve" path="res://addons/dialogue_manager/components/dialogue_editor_property/editor_property_control.gd" id="1_het12"]
|
||||
[ext_resource type="PackedScene" uid="uid://b16uuqjuof3n5" path="res://addons/dialogue_manager/components/dialogue_editor_property/resource_button.tscn" id="2_hh3d4"]
|
||||
[ext_resource type="PackedScene" uid="uid://dnufpcdrreva3" path="res://addons/dialogue_manager/components/files_list.tscn" id="3_l8fp6"]
|
||||
|
||||
[node name="PropertyEditorButton" type="HBoxContainer" unique_id=94139718]
|
||||
offset_right = 40.0
|
||||
offset_bottom = 40.0
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/separation = 0
|
||||
script = ExtResource("1_het12")
|
||||
|
||||
[node name="ResourceButton" parent="." unique_id=371241282 instance=ExtResource("2_hh3d4")]
|
||||
layout_mode = 2
|
||||
text = "<empty>"
|
||||
flat = true
|
||||
text_overrun_behavior = 3
|
||||
clip_text = true
|
||||
|
||||
[node name="MenuButton" type="Button" parent="." unique_id=1528906238]
|
||||
layout_mode = 2
|
||||
flat = true
|
||||
|
||||
[node name="Menu" type="PopupMenu" parent="." unique_id=1401460802]
|
||||
|
||||
[node name="QuickOpenDialog" type="ConfirmationDialog" parent="." unique_id=1059747349]
|
||||
title = "Find Dialogue Resource"
|
||||
size = Vector2i(400, 600)
|
||||
min_size = Vector2i(400, 600)
|
||||
ok_button_text = "Open"
|
||||
|
||||
[node name="FilesList" parent="QuickOpenDialog" unique_id=950831035 instance=ExtResource("3_l8fp6")]
|
||||
|
||||
[node name="NewDialog" type="FileDialog" parent="." unique_id=317264302]
|
||||
size = Vector2i(900, 750)
|
||||
min_size = Vector2i(900, 750)
|
||||
dialog_hide_on_ok = true
|
||||
filters = PackedStringArray("*.dialogue ; Dialogue")
|
||||
|
||||
[node name="OpenDialog" type="FileDialog" parent="." unique_id=641895417]
|
||||
title = "Open a File"
|
||||
size = Vector2i(900, 750)
|
||||
min_size = Vector2i(900, 750)
|
||||
ok_button_text = "Open"
|
||||
dialog_hide_on_ok = true
|
||||
file_mode = 0
|
||||
filters = PackedStringArray("*.dialogue ; Dialogue")
|
||||
|
||||
[connection signal="pressed" from="ResourceButton" to="." method="_on_resource_button_pressed"]
|
||||
[connection signal="resource_dropped" from="ResourceButton" to="." method="_on_resource_button_resource_dropped"]
|
||||
[connection signal="pressed" from="MenuButton" to="." method="_on_menu_button_pressed"]
|
||||
[connection signal="id_pressed" from="Menu" to="." method="_on_menu_id_pressed"]
|
||||
[connection signal="confirmed" from="QuickOpenDialog" to="." method="_on_quick_open_dialog_confirmed"]
|
||||
[connection signal="file_double_clicked" from="QuickOpenDialog/FilesList" to="." method="_on_files_list_file_double_clicked"]
|
||||
[connection signal="file_selected" from="QuickOpenDialog/FilesList" to="." method="_on_files_list_file_selected"]
|
||||
[connection signal="file_selected" from="NewDialog" to="." method="_on_new_dialog_file_selected"]
|
||||
[connection signal="canceled" from="OpenDialog" to="." method="_on_file_dialog_canceled"]
|
||||
[connection signal="file_selected" from="OpenDialog" to="." method="_on_open_dialog_file_selected"]
|
||||
@@ -0,0 +1,48 @@
|
||||
@tool
|
||||
extends Button
|
||||
|
||||
|
||||
signal resource_dropped(next_resource: Resource)
|
||||
|
||||
|
||||
var resource: Resource:
|
||||
set(next_resource):
|
||||
resource = next_resource
|
||||
if resource:
|
||||
icon = DMPlugin.instance._get_plugin_icon()
|
||||
text = resource.resource_path.get_file().replace(".dialogue", "")
|
||||
else:
|
||||
icon = null
|
||||
text = "<empty>"
|
||||
get:
|
||||
return resource
|
||||
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
match what:
|
||||
NOTIFICATION_DRAG_BEGIN:
|
||||
var data: Variant = get_viewport().gui_get_drag_data()
|
||||
if typeof(data) == TYPE_DICTIONARY and data.type == "files" and data.files.size() > 0 and data.files[0].ends_with(".dialogue"):
|
||||
add_theme_stylebox_override("normal", get_theme_stylebox("focus", "LineEdit"))
|
||||
add_theme_stylebox_override("hover", get_theme_stylebox("focus", "LineEdit"))
|
||||
|
||||
NOTIFICATION_DRAG_END:
|
||||
self.resource = resource
|
||||
remove_theme_stylebox_override("normal")
|
||||
remove_theme_stylebox_override("hover")
|
||||
|
||||
|
||||
func _can_drop_data(_at_position: Vector2, data: Variant) -> bool:
|
||||
if typeof(data) != TYPE_DICTIONARY: return false
|
||||
if data.type != "files": return false
|
||||
|
||||
var files: PackedStringArray = Array(data.files).filter(func(f: String) -> bool: return f.get_extension() == "dialogue")
|
||||
return files.size() > 0
|
||||
|
||||
|
||||
func _drop_data(_at_position: Vector2, data: Variant) -> void:
|
||||
var files: PackedStringArray = Array(data.files).filter(func(f: String) -> bool: return f.get_extension() == "dialogue")
|
||||
|
||||
if files.size() == 0: return
|
||||
|
||||
resource_dropped.emit(load(files[0]))
|
||||
@@ -0,0 +1 @@
|
||||
uid://damhqta55t67c
|
||||
@@ -0,0 +1,9 @@
|
||||
[gd_scene format=3 uid="uid://b16uuqjuof3n5"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://damhqta55t67c" path="res://addons/dialogue_manager/components/dialogue_editor_property/resource_button.gd" id="1_7u2i7"]
|
||||
|
||||
[node name="ResourceButton" type="Button"]
|
||||
offset_right = 8.0
|
||||
offset_bottom = 8.0
|
||||
size_flags_horizontal = 3
|
||||
script = ExtResource("1_7u2i7")
|
||||
@@ -0,0 +1,85 @@
|
||||
@tool
|
||||
extends Control
|
||||
|
||||
|
||||
signal failed()
|
||||
signal updated(updated_to_version: String)
|
||||
|
||||
|
||||
const TEMP_FILE_NAME: String = "user://temp.zip"
|
||||
|
||||
|
||||
@onready var logo: TextureRect = %Logo
|
||||
@onready var label: Label = $VBox/Label
|
||||
@onready var http_request: HTTPRequest = $HTTPRequest
|
||||
@onready var download_button: Button = %DownloadButton
|
||||
|
||||
var next_version_release: Dictionary:
|
||||
set(value):
|
||||
next_version_release = value
|
||||
label.text = DMConstants.translate(&"update.is_available_for_download") % value.tag_name.substr(1)
|
||||
get:
|
||||
return next_version_release
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
$VBox/Center/DownloadButton.text = DMConstants.translate(&"update.download_update")
|
||||
$VBox/Center2/NotesButton.text = DMConstants.translate(&"update.release_notes")
|
||||
|
||||
|
||||
#region Signals
|
||||
|
||||
|
||||
func _on_download_button_pressed() -> void:
|
||||
# Safeguard the actual dialogue manager repo from accidentally updating itself
|
||||
if FileAccess.file_exists("res://tests/test_basic_dialogue.gd"):
|
||||
prints("You can't update the addon from within itself.")
|
||||
failed.emit()
|
||||
return
|
||||
|
||||
http_request.request(next_version_release.zipball_url)
|
||||
download_button.disabled = true
|
||||
download_button.text = DMConstants.translate(&"update.downloading")
|
||||
|
||||
|
||||
func _on_http_request_request_completed(result: int, _response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
|
||||
if result != HTTPRequest.RESULT_SUCCESS:
|
||||
failed.emit()
|
||||
return
|
||||
|
||||
# Save the downloaded zip
|
||||
var zip_file: FileAccess = FileAccess.open(TEMP_FILE_NAME, FileAccess.WRITE)
|
||||
zip_file.store_buffer(body)
|
||||
zip_file.close()
|
||||
|
||||
OS.move_to_trash(ProjectSettings.globalize_path("res://addons/dialogue_manager"))
|
||||
|
||||
var zip_reader: ZIPReader = ZIPReader.new()
|
||||
zip_reader.open(TEMP_FILE_NAME)
|
||||
var files: PackedStringArray = zip_reader.get_files()
|
||||
|
||||
var base_path: String = files[1]
|
||||
# Remove archive folder
|
||||
files.remove_at(0)
|
||||
# Remove assets folder
|
||||
files.remove_at(0)
|
||||
|
||||
for path: String in files:
|
||||
var new_file_path: String = path.replace(base_path, "")
|
||||
if path.ends_with("/"):
|
||||
DirAccess.make_dir_recursive_absolute("res://addons/%s" % new_file_path)
|
||||
else:
|
||||
var file: FileAccess = FileAccess.open("res://addons/%s" % new_file_path, FileAccess.WRITE)
|
||||
file.store_buffer(zip_reader.read_file(path))
|
||||
|
||||
zip_reader.close()
|
||||
DirAccess.remove_absolute(TEMP_FILE_NAME)
|
||||
|
||||
updated.emit(next_version_release.tag_name.substr(1))
|
||||
|
||||
|
||||
func _on_notes_button_pressed() -> void:
|
||||
OS.shell_open(next_version_release.html_url)
|
||||
|
||||
|
||||
#endregion
|
||||
@@ -0,0 +1 @@
|
||||
uid://kpwo418lb2t2
|
||||
@@ -0,0 +1,60 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://qdxrxv3c3hxk"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://kpwo418lb2t2" path="res://addons/dialogue_manager/components/download_update_panel.gd" id="1_4tm1k"]
|
||||
[ext_resource type="Texture2D" uid="uid://d3baj6rygkb3f" path="res://addons/dialogue_manager/assets/update.svg" id="2_4o2m6"]
|
||||
|
||||
[node name="DownloadUpdatePanel" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_4tm1k")
|
||||
|
||||
[node name="HTTPRequest" type="HTTPRequest" parent="."]
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -1.0
|
||||
offset_top = 9.0
|
||||
offset_right = -1.0
|
||||
offset_bottom = 9.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_constants/separation = 10
|
||||
|
||||
[node name="Logo" type="TextureRect" parent="VBox"]
|
||||
unique_name_in_owner = true
|
||||
clip_contents = true
|
||||
custom_minimum_size = Vector2(300, 80)
|
||||
layout_mode = 2
|
||||
texture = ExtResource("2_4o2m6")
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="Label" type="Label" parent="VBox"]
|
||||
layout_mode = 2
|
||||
text = "v1.2.3 is available for download."
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="Center" type="CenterContainer" parent="VBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="DownloadButton" type="Button" parent="VBox/Center"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Download update"
|
||||
|
||||
[node name="Center2" type="CenterContainer" parent="VBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="NotesButton" type="LinkButton" parent="VBox/Center2"]
|
||||
layout_mode = 2
|
||||
text = "Read release notes"
|
||||
|
||||
[connection signal="request_completed" from="HTTPRequest" to="." method="_on_http_request_request_completed"]
|
||||
[connection signal="pressed" from="VBox/Center/DownloadButton" to="." method="_on_download_button_pressed"]
|
||||
[connection signal="pressed" from="VBox/Center2/NotesButton" to="." method="_on_notes_button_pressed"]
|
||||
@@ -0,0 +1,83 @@
|
||||
@tool
|
||||
extends HBoxContainer
|
||||
|
||||
|
||||
signal error_pressed(line_number: int)
|
||||
|
||||
|
||||
@onready var error_button: Button = $ErrorButton
|
||||
@onready var next_button: Button = $NextButton
|
||||
@onready var count_label: Label = $CountLabel
|
||||
@onready var previous_button: Button = $PreviousButton
|
||||
|
||||
## The index of the current error being shown
|
||||
var error_index: int = 0:
|
||||
set(next_error_index):
|
||||
error_index = wrap(next_error_index, 0, errors.size())
|
||||
show_error()
|
||||
get:
|
||||
return error_index
|
||||
|
||||
## The list of all errors
|
||||
var errors: Array = []:
|
||||
set(next_errors):
|
||||
errors = next_errors
|
||||
self.error_index = 0
|
||||
get:
|
||||
return errors
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
apply_theme()
|
||||
hide()
|
||||
|
||||
|
||||
## Set up colors and icons
|
||||
func apply_theme() -> void:
|
||||
error_button.add_theme_color_override("font_color", get_theme_color("error_color", "Editor"))
|
||||
error_button.add_theme_color_override("font_hover_color", get_theme_color("error_color", "Editor"))
|
||||
error_button.icon = get_theme_icon("StatusError", "EditorIcons")
|
||||
previous_button.icon = get_theme_icon("ArrowLeft", "EditorIcons")
|
||||
next_button.icon = get_theme_icon("ArrowRight", "EditorIcons")
|
||||
|
||||
|
||||
## Move the error index to match a given line
|
||||
func show_error_for_line_number(line_number: int) -> void:
|
||||
for i: int in range(0, errors.size()):
|
||||
if errors[i].line_number == line_number:
|
||||
self.error_index = i
|
||||
|
||||
|
||||
## Show the current error
|
||||
func show_error() -> void:
|
||||
if errors.size() == 0:
|
||||
hide()
|
||||
else:
|
||||
show()
|
||||
count_label.text = DMConstants.translate(&"n_of_n").format({ index = error_index + 1, total = errors.size() })
|
||||
var error: DMError = errors[error_index]
|
||||
error_button.text = DMConstants.translate(&"errors.line_and_message").format({ line = error.line_number, column = error.column_number, message = DMConstants.get_error_message(error.error) })
|
||||
|
||||
|
||||
#region Signals
|
||||
|
||||
|
||||
func _on_errors_panel_theme_changed() -> void:
|
||||
apply_theme()
|
||||
|
||||
|
||||
func _on_error_button_pressed() -> void:
|
||||
error_pressed.emit(errors[error_index].line_number, errors[error_index].column_number)
|
||||
|
||||
|
||||
func _on_previous_button_pressed() -> void:
|
||||
self.error_index -= 1
|
||||
_on_error_button_pressed()
|
||||
|
||||
|
||||
func _on_next_button_pressed() -> void:
|
||||
self.error_index += 1
|
||||
_on_error_button_pressed()
|
||||
|
||||
|
||||
#endregion
|
||||
@@ -0,0 +1 @@
|
||||
uid://d2l8nlb6hhrfp
|
||||
@@ -0,0 +1,56 @@
|
||||
[gd_scene load_steps=4 format=3 uid="uid://cs8pwrxr5vxix"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://d2l8nlb6hhrfp" path="res://addons/dialogue_manager/components/errors_panel.gd" id="1_nfm3c"]
|
||||
|
||||
[sub_resource type="Image" id="Image_w0gko"]
|
||||
data = {
|
||||
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 131, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 93, 93, 55, 255, 97, 97, 58, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 98, 98, 47, 255, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 94, 94, 46, 255, 93, 93, 236, 255, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
|
||||
"format": "RGBA8",
|
||||
"height": 16,
|
||||
"mipmaps": false,
|
||||
"width": 16
|
||||
}
|
||||
|
||||
[sub_resource type="ImageTexture" id="ImageTexture_s6fxl"]
|
||||
image = SubResource("Image_w0gko")
|
||||
|
||||
[node name="ErrorsPanel" type="HBoxContainer"]
|
||||
visible = false
|
||||
offset_right = 1024.0
|
||||
offset_bottom = 600.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_nfm3c")
|
||||
metadata/_edit_layout_mode = 1
|
||||
|
||||
[node name="ErrorButton" type="Button" parent="."]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_colors/font_color = Color(0, 0, 0, 1)
|
||||
theme_override_colors/font_hover_color = Color(0, 0, 0, 1)
|
||||
theme_override_constants/h_separation = 3
|
||||
icon = SubResource("ImageTexture_s6fxl")
|
||||
flat = true
|
||||
alignment = 0
|
||||
text_overrun_behavior = 4
|
||||
|
||||
[node name="Spacer" type="Control" parent="."]
|
||||
custom_minimum_size = Vector2(40, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="PreviousButton" type="Button" parent="."]
|
||||
layout_mode = 2
|
||||
icon = SubResource("ImageTexture_s6fxl")
|
||||
flat = true
|
||||
|
||||
[node name="CountLabel" type="Label" parent="."]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="NextButton" type="Button" parent="."]
|
||||
layout_mode = 2
|
||||
icon = SubResource("ImageTexture_s6fxl")
|
||||
flat = true
|
||||
|
||||
[connection signal="pressed" from="ErrorButton" to="." method="_on_error_button_pressed"]
|
||||
[connection signal="pressed" from="PreviousButton" to="." method="_on_previous_button_pressed"]
|
||||
[connection signal="pressed" from="NextButton" to="." method="_on_next_button_pressed"]
|
||||
@@ -0,0 +1,151 @@
|
||||
@tool
|
||||
extends VBoxContainer
|
||||
|
||||
|
||||
signal file_selected(file_path: String)
|
||||
signal file_popup_menu_requested(at_position: Vector2)
|
||||
signal file_double_clicked(file_path: String)
|
||||
signal file_middle_clicked(file_path: String)
|
||||
|
||||
|
||||
const MODIFIED_SUFFIX: String = "(*)"
|
||||
|
||||
|
||||
@export var icon: Texture2D
|
||||
|
||||
@onready var filter_edit: LineEdit = $FilterEdit
|
||||
@onready var list: ItemList = $List
|
||||
|
||||
var file_map: Dictionary = {}
|
||||
|
||||
var current_file_path: String = ""
|
||||
var last_selected_file_path: String = ""
|
||||
|
||||
var files: PackedStringArray = []:
|
||||
set(next_files):
|
||||
files = next_files
|
||||
files.sort()
|
||||
update_file_map()
|
||||
apply_filter()
|
||||
get:
|
||||
return files
|
||||
|
||||
var unsaved_files: Array[String] = []
|
||||
|
||||
var filter: String = "":
|
||||
set(next_filter):
|
||||
filter = next_filter
|
||||
apply_filter()
|
||||
get:
|
||||
return filter
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
apply_theme()
|
||||
|
||||
filter_edit.placeholder_text = DMConstants.translate(&"files_list.filter")
|
||||
|
||||
|
||||
func focus_filter() -> void:
|
||||
filter_edit.grab_focus()
|
||||
|
||||
|
||||
func select_file(file: String) -> void:
|
||||
list.deselect_all()
|
||||
for i: int in range(0, list.get_item_count()):
|
||||
var item_text: String = list.get_item_text(i).replace(MODIFIED_SUFFIX, "")
|
||||
if item_text == get_nice_file(file, item_text.count("/") + 1):
|
||||
list.select(i)
|
||||
last_selected_file_path = file
|
||||
|
||||
|
||||
func mark_file_as_unsaved(file: String, is_unsaved: bool) -> void:
|
||||
if not file in unsaved_files and is_unsaved:
|
||||
unsaved_files.append(file)
|
||||
elif file in unsaved_files and not is_unsaved:
|
||||
unsaved_files.erase(file)
|
||||
apply_filter()
|
||||
|
||||
|
||||
func update_file_map() -> void:
|
||||
file_map = {}
|
||||
for file: String in files:
|
||||
var nice_file: String = get_nice_file(file)
|
||||
|
||||
# See if a value with just the file name is already in the map
|
||||
for key: String in file_map.keys():
|
||||
if file_map[key] == nice_file:
|
||||
var bit_count: int = nice_file.count("/") + 2
|
||||
|
||||
var existing_nice_file: String = get_nice_file(key, bit_count)
|
||||
nice_file = get_nice_file(file, bit_count)
|
||||
|
||||
while nice_file == existing_nice_file:
|
||||
bit_count += 1
|
||||
existing_nice_file = get_nice_file(key, bit_count)
|
||||
nice_file = get_nice_file(file, bit_count)
|
||||
|
||||
file_map[key] = existing_nice_file
|
||||
|
||||
file_map[file] = nice_file
|
||||
|
||||
|
||||
func get_nice_file(file_path: String, path_bit_count: int = 1) -> String:
|
||||
var bits: PackedStringArray = file_path.replace("res://", "").replace(".dialogue", "").split("/")
|
||||
bits = bits.slice(-path_bit_count)
|
||||
return "/".join(bits)
|
||||
|
||||
|
||||
func apply_filter() -> void:
|
||||
list.clear()
|
||||
for file: String in file_map.keys():
|
||||
if filter == "" or filter.to_lower() in file.to_lower():
|
||||
var nice_file: String = file_map[file]
|
||||
if file in unsaved_files:
|
||||
nice_file += MODIFIED_SUFFIX
|
||||
var new_id: int = list.add_item(nice_file)
|
||||
list.set_item_icon(new_id, icon)
|
||||
|
||||
select_file(current_file_path)
|
||||
|
||||
|
||||
func apply_theme() -> void:
|
||||
if is_instance_valid(filter_edit):
|
||||
filter_edit.right_icon = get_theme_icon("Search", "EditorIcons")
|
||||
if is_instance_valid(list):
|
||||
list.add_theme_stylebox_override("panel", get_theme_stylebox("panel", "Panel"))
|
||||
|
||||
|
||||
#region Signals
|
||||
|
||||
|
||||
func _on_theme_changed() -> void:
|
||||
apply_theme()
|
||||
|
||||
|
||||
func _on_filter_edit_text_changed(new_text: String) -> void:
|
||||
self.filter = new_text
|
||||
|
||||
|
||||
func _on_list_item_clicked(index: int, at_position: Vector2, mouse_button_index: int) -> void:
|
||||
var item_text: String = list.get_item_text(index).replace(MODIFIED_SUFFIX, "")
|
||||
var file: String = file_map.find_key(item_text)
|
||||
|
||||
if mouse_button_index == MOUSE_BUTTON_LEFT or mouse_button_index == MOUSE_BUTTON_RIGHT:
|
||||
select_file(file)
|
||||
file_selected.emit(file)
|
||||
if mouse_button_index == MOUSE_BUTTON_RIGHT:
|
||||
file_popup_menu_requested.emit(at_position)
|
||||
|
||||
if mouse_button_index == MOUSE_BUTTON_MIDDLE:
|
||||
file_middle_clicked.emit(file)
|
||||
|
||||
|
||||
func _on_list_item_activated(index: int) -> void:
|
||||
var item_text: String = list.get_item_text(index).replace(MODIFIED_SUFFIX, "")
|
||||
var file: String = file_map.find_key(item_text)
|
||||
select_file(file)
|
||||
file_double_clicked.emit(file)
|
||||
|
||||
|
||||
#endregion
|
||||
@@ -0,0 +1 @@
|
||||
uid://dqa4a4wwoo0aa
|
||||
@@ -0,0 +1,29 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://dnufpcdrreva3"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dqa4a4wwoo0aa" path="res://addons/dialogue_manager/components/files_list.gd" id="1_cytii"]
|
||||
[ext_resource type="Texture2D" uid="uid://d3lr2uas6ax8v" path="res://addons/dialogue_manager/assets/icon.svg" id="2_3ijx1"]
|
||||
|
||||
[node name="FilesList" type="VBoxContainer"]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_vertical = 3
|
||||
script = ExtResource("1_cytii")
|
||||
icon = ExtResource("2_3ijx1")
|
||||
|
||||
[node name="FilterEdit" type="LineEdit" parent="."]
|
||||
layout_mode = 2
|
||||
placeholder_text = "Filter files"
|
||||
clear_button_enabled = true
|
||||
|
||||
[node name="List" type="ItemList" parent="."]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
allow_rmb_select = true
|
||||
|
||||
[connection signal="theme_changed" from="." to="." method="_on_theme_changed"]
|
||||
[connection signal="text_changed" from="FilterEdit" to="." method="_on_filter_edit_text_changed"]
|
||||
[connection signal="item_activated" from="List" to="." method="_on_list_item_activated"]
|
||||
[connection signal="item_clicked" from="List" to="." method="_on_list_item_clicked"]
|
||||
@@ -0,0 +1,22 @@
|
||||
class_name DMPreviewGenerator extends EditorResourcePreviewGenerator
|
||||
|
||||
|
||||
const ICON: Texture2D = preload("../assets/resource.svg")
|
||||
|
||||
|
||||
func _handles(type: String) -> bool:
|
||||
return type == "Resource"
|
||||
|
||||
|
||||
func _generate(resource: Resource, size: Vector2i, _metadata: Dictionary) -> Texture2D:
|
||||
if resource is DialogueResource:
|
||||
var image: Image = ICON.get_image()
|
||||
image.resize(size.x, size.y, Image.INTERPOLATE_LANCZOS)
|
||||
return ImageTexture.create_from_image(image)
|
||||
|
||||
|
||||
return null
|
||||
|
||||
|
||||
func _generate_small_preview_automatically() -> bool:
|
||||
return true
|
||||
@@ -0,0 +1 @@
|
||||
uid://b5te0ncm02f2b
|
||||
@@ -0,0 +1,219 @@
|
||||
@tool
|
||||
extends VBoxContainer
|
||||
|
||||
|
||||
signal open_requested()
|
||||
signal close_requested()
|
||||
|
||||
|
||||
@onready var input: LineEdit = $Search/Input
|
||||
@onready var result_label: Label = $Search/ResultLabel
|
||||
@onready var previous_button: Button = $Search/PreviousButton
|
||||
@onready var next_button: Button = $Search/NextButton
|
||||
@onready var match_case_button: CheckBox = $Search/MatchCaseCheckBox
|
||||
@onready var replace_check_button: CheckButton = $Search/ReplaceCheckButton
|
||||
@onready var replace_panel: HBoxContainer = $Replace
|
||||
@onready var replace_input: LineEdit = $Replace/Input
|
||||
@onready var replace_button: Button = $Replace/ReplaceButton
|
||||
@onready var replace_all_button: Button = $Replace/ReplaceAllButton
|
||||
|
||||
# The code edit we will be affecting (for some reason exporting this didn't work)
|
||||
var code_edit: CodeEdit:
|
||||
set(next_code_edit):
|
||||
code_edit = next_code_edit
|
||||
code_edit.gui_input.connect(_on_text_edit_gui_input)
|
||||
code_edit.text_changed.connect(_on_text_edit_text_changed)
|
||||
get:
|
||||
return code_edit
|
||||
|
||||
var results: Array = []
|
||||
var result_index: int = -1:
|
||||
set(next_result_index):
|
||||
result_index = next_result_index
|
||||
if results.size() > 0:
|
||||
var r: Array = results[result_index]
|
||||
code_edit.set_caret_line(r[0])
|
||||
code_edit.select(r[0], r[1], r[0], r[1] + r[2])
|
||||
code_edit.center_viewport_to_caret()
|
||||
else:
|
||||
result_index = -1
|
||||
if is_instance_valid(code_edit):
|
||||
code_edit.deselect()
|
||||
|
||||
result_label.text = DMConstants.translate(&"n_of_n").format({ index = result_index + 1, total = results.size() })
|
||||
get:
|
||||
return result_index
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
apply_theme()
|
||||
|
||||
input.placeholder_text = DMConstants.translate(&"search.placeholder")
|
||||
previous_button.tooltip_text = DMConstants.translate(&"search.previous")
|
||||
next_button.tooltip_text = DMConstants.translate(&"search.next")
|
||||
match_case_button.text = DMConstants.translate(&"search.match_case")
|
||||
$Search/ReplaceCheckButton.text = DMConstants.translate(&"search.toggle_replace")
|
||||
replace_button.text = DMConstants.translate(&"search.replace")
|
||||
replace_all_button.text = DMConstants.translate(&"search.replace_all")
|
||||
$Replace/ReplaceLabel.text = DMConstants.translate(&"search.replace_with")
|
||||
|
||||
self.result_index = -1
|
||||
|
||||
replace_panel.hide()
|
||||
replace_button.disabled = true
|
||||
replace_all_button.disabled = true
|
||||
|
||||
hide()
|
||||
|
||||
|
||||
func focus_line_edit() -> void:
|
||||
input.grab_focus()
|
||||
input.select_all()
|
||||
search()
|
||||
|
||||
|
||||
func apply_theme() -> void:
|
||||
if is_instance_valid(previous_button):
|
||||
previous_button.icon = get_theme_icon("ArrowLeft", "EditorIcons")
|
||||
if is_instance_valid(next_button):
|
||||
next_button.icon = get_theme_icon("ArrowRight", "EditorIcons")
|
||||
|
||||
|
||||
# Find text in the code
|
||||
func search(text: String = "", default_result_index: int = 0) -> void:
|
||||
results.clear()
|
||||
|
||||
if text == "":
|
||||
text = input.text
|
||||
|
||||
var lines: PackedStringArray = code_edit.text.split("\n")
|
||||
for line_number: int in range(0, lines.size()):
|
||||
var line: String = lines[line_number]
|
||||
|
||||
var column: int = find_in_line(line, text, 0)
|
||||
while column > -1:
|
||||
results.append([line_number, column, text.length()])
|
||||
column = find_in_line(line, text, column + 1)
|
||||
|
||||
if results.size() > 0:
|
||||
replace_button.disabled = false
|
||||
replace_all_button.disabled = false
|
||||
else:
|
||||
replace_button.disabled = true
|
||||
replace_all_button.disabled = true
|
||||
|
||||
self.result_index = clamp(default_result_index, 0, results.size() - 1)
|
||||
|
||||
|
||||
# Find text in a string and match case if requested
|
||||
func find_in_line(line: String, text: String, from_index: int = 0) -> int:
|
||||
if match_case_button.button_pressed:
|
||||
return line.find(text, from_index)
|
||||
else:
|
||||
return line.findn(text, from_index)
|
||||
|
||||
|
||||
#region Signals
|
||||
|
||||
|
||||
func _on_text_edit_gui_input(event: InputEvent) -> void:
|
||||
if event is InputEventKey and event.is_pressed():
|
||||
match event.as_text():
|
||||
"Ctrl+F", "Command+F":
|
||||
open_requested.emit()
|
||||
get_viewport().set_input_as_handled()
|
||||
"Ctrl+Shift+R", "Command+Shift+R":
|
||||
replace_check_button.set_pressed(true)
|
||||
open_requested.emit()
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
func _on_text_edit_text_changed() -> void:
|
||||
results.clear()
|
||||
|
||||
|
||||
func _on_search_and_replace_theme_changed() -> void:
|
||||
apply_theme()
|
||||
|
||||
|
||||
func _on_input_text_changed(new_text: String) -> void:
|
||||
search(new_text)
|
||||
|
||||
|
||||
func _on_previous_button_pressed() -> void:
|
||||
self.result_index = wrapi(result_index - 1, 0, results.size())
|
||||
|
||||
|
||||
func _on_next_button_pressed() -> void:
|
||||
self.result_index = wrapi(result_index + 1, 0, results.size())
|
||||
|
||||
|
||||
func _on_search_and_replace_visibility_changed() -> void:
|
||||
if is_instance_valid(input):
|
||||
if visible:
|
||||
input.grab_focus()
|
||||
var selection: String = code_edit.get_selected_text()
|
||||
if input.text == "" and selection != "":
|
||||
input.text = selection
|
||||
search(selection)
|
||||
else:
|
||||
search()
|
||||
else:
|
||||
input.text = ""
|
||||
|
||||
|
||||
func _on_input_gui_input(event: InputEvent) -> void:
|
||||
if event is InputEventKey and event.is_pressed():
|
||||
match event.as_text():
|
||||
"Enter":
|
||||
if results.size() == 0:
|
||||
search(input.text)
|
||||
self.result_index = wrapi(result_index + 1, 0, results.size())
|
||||
"Escape":
|
||||
emit_signal("close_requested")
|
||||
|
||||
|
||||
func _on_replace_button_pressed() -> void:
|
||||
if result_index == -1: return
|
||||
|
||||
# Replace the selection at result index
|
||||
var r: Array = results[result_index]
|
||||
code_edit.begin_complex_operation()
|
||||
var lines: PackedStringArray = code_edit.text.split("\n")
|
||||
var line: String = lines[r[0]]
|
||||
line = line.substr(0, r[1]) + replace_input.text + line.substr(r[1] + r[2])
|
||||
lines[r[0]] = line
|
||||
code_edit.text = "\n".join(lines)
|
||||
code_edit.end_complex_operation()
|
||||
code_edit.text_changed.emit()
|
||||
|
||||
search(input.text, result_index)
|
||||
|
||||
|
||||
func _on_replace_all_button_pressed() -> void:
|
||||
if match_case_button.button_pressed:
|
||||
code_edit.text = code_edit.text.replace(input.text, replace_input.text)
|
||||
else:
|
||||
code_edit.text = code_edit.text.replacen(input.text, replace_input.text)
|
||||
search()
|
||||
code_edit.text_changed.emit()
|
||||
|
||||
|
||||
func _on_replace_check_button_toggled(button_pressed: bool) -> void:
|
||||
replace_panel.visible = button_pressed
|
||||
if button_pressed:
|
||||
replace_input.grab_focus()
|
||||
|
||||
|
||||
func _on_input_focus_entered() -> void:
|
||||
if results.size() == 0:
|
||||
search()
|
||||
else:
|
||||
self.result_index = result_index
|
||||
|
||||
|
||||
func _on_match_case_check_box_toggled(button_pressed: bool) -> void:
|
||||
search()
|
||||
|
||||
|
||||
#endregion
|
||||
@@ -0,0 +1 @@
|
||||
uid://cijsmjkq21cdq
|
||||
@@ -0,0 +1,87 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://gr8nakpbrhby"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cijsmjkq21cdq" path="res://addons/dialogue_manager/components/search_and_replace.gd" id="1_8oj1f"]
|
||||
|
||||
[node name="SearchAndReplace" type="VBoxContainer"]
|
||||
visible = false
|
||||
anchors_preset = 10
|
||||
anchor_right = 1.0
|
||||
offset_bottom = 31.0
|
||||
grow_horizontal = 2
|
||||
size_flags_horizontal = 3
|
||||
script = ExtResource("1_8oj1f")
|
||||
|
||||
[node name="Search" type="HBoxContainer" parent="."]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Input" type="LineEdit" parent="Search"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
placeholder_text = "Text to search for"
|
||||
metadata/_edit_use_custom_anchors = true
|
||||
|
||||
[node name="MatchCaseCheckBox" type="CheckBox" parent="Search"]
|
||||
layout_mode = 2
|
||||
text = "Match case"
|
||||
|
||||
[node name="VSeparator" type="VSeparator" parent="Search"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="PreviousButton" type="Button" parent="Search"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Previous"
|
||||
flat = true
|
||||
|
||||
[node name="ResultLabel" type="Label" parent="Search"]
|
||||
layout_mode = 2
|
||||
text = "0 of 0"
|
||||
|
||||
[node name="NextButton" type="Button" parent="Search"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Next"
|
||||
flat = true
|
||||
|
||||
[node name="VSeparator2" type="VSeparator" parent="Search"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ReplaceCheckButton" type="CheckButton" parent="Search"]
|
||||
layout_mode = 2
|
||||
text = "Replace"
|
||||
|
||||
[node name="Replace" type="HBoxContainer" parent="."]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ReplaceLabel" type="Label" parent="Replace"]
|
||||
layout_mode = 2
|
||||
text = "Replace with:"
|
||||
|
||||
[node name="Input" type="LineEdit" parent="Replace"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ReplaceButton" type="Button" parent="Replace"]
|
||||
layout_mode = 2
|
||||
disabled = true
|
||||
text = "Replace"
|
||||
flat = true
|
||||
|
||||
[node name="ReplaceAllButton" type="Button" parent="Replace"]
|
||||
layout_mode = 2
|
||||
disabled = true
|
||||
text = "Replace all"
|
||||
flat = true
|
||||
|
||||
[connection signal="theme_changed" from="." to="." method="_on_search_and_replace_theme_changed"]
|
||||
[connection signal="visibility_changed" from="." to="." method="_on_search_and_replace_visibility_changed"]
|
||||
[connection signal="focus_entered" from="Search/Input" to="." method="_on_input_focus_entered"]
|
||||
[connection signal="gui_input" from="Search/Input" to="." method="_on_input_gui_input"]
|
||||
[connection signal="text_changed" from="Search/Input" to="." method="_on_input_text_changed"]
|
||||
[connection signal="toggled" from="Search/MatchCaseCheckBox" to="." method="_on_match_case_check_box_toggled"]
|
||||
[connection signal="pressed" from="Search/PreviousButton" to="." method="_on_previous_button_pressed"]
|
||||
[connection signal="pressed" from="Search/NextButton" to="." method="_on_next_button_pressed"]
|
||||
[connection signal="toggled" from="Search/ReplaceCheckButton" to="." method="_on_replace_check_button_toggled"]
|
||||
[connection signal="focus_entered" from="Replace/Input" to="." method="_on_input_focus_entered"]
|
||||
[connection signal="gui_input" from="Replace/Input" to="." method="_on_input_gui_input"]
|
||||
[connection signal="pressed" from="Replace/ReplaceButton" to="." method="_on_replace_button_pressed"]
|
||||
[connection signal="pressed" from="Replace/ReplaceAllButton" to="." method="_on_replace_all_button_pressed"]
|
||||
@@ -0,0 +1,126 @@
|
||||
@tool
|
||||
extends Button
|
||||
|
||||
|
||||
const REMOTE_RELEASES_URL: String = "https://api.github.com/repos/nathanhoad/godot_dialogue_manager/releases"
|
||||
|
||||
|
||||
@onready var http_request: HTTPRequest = $HTTPRequest
|
||||
@onready var download_dialog: AcceptDialog = $DownloadDialog
|
||||
@onready var download_update_panel: Control = $DownloadDialog/DownloadUpdatePanel
|
||||
@onready var needs_reload_dialog: AcceptDialog = $NeedsReloadDialog
|
||||
@onready var update_failed_dialog: AcceptDialog = $UpdateFailedDialog
|
||||
@onready var timer: Timer = $Timer
|
||||
|
||||
var needs_reload: bool = false
|
||||
|
||||
# A lambda that gets called just before refreshing the plugin. Return false to stop the reload.
|
||||
var on_before_refresh: Callable = func() -> bool: return true
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
hide()
|
||||
apply_theme()
|
||||
|
||||
# Check for updates on GitHub
|
||||
check_for_update()
|
||||
|
||||
# Check again every few hours
|
||||
timer.start(60 * 60 * 12)
|
||||
|
||||
|
||||
# Convert a version number to an actually comparable number
|
||||
func version_to_number(version: String) -> int:
|
||||
var bits: PackedStringArray = version.split(".")
|
||||
return bits[0].to_int() * 1000000 + bits[1].to_int() * 1000 + bits[2].to_int()
|
||||
|
||||
|
||||
func apply_theme() -> void:
|
||||
var color: Color = get_theme_color("success_color", "Editor")
|
||||
|
||||
if needs_reload:
|
||||
color = get_theme_color("error_color", "Editor")
|
||||
icon = get_theme_icon("Reload", "EditorIcons")
|
||||
add_theme_color_override("icon_normal_color", color)
|
||||
add_theme_color_override("icon_focus_color", color)
|
||||
add_theme_color_override("icon_hover_color", color)
|
||||
|
||||
add_theme_color_override("font_color", color)
|
||||
add_theme_color_override("font_focus_color", color)
|
||||
add_theme_color_override("font_hover_color", color)
|
||||
|
||||
|
||||
func check_for_update() -> void:
|
||||
if DMSettings.get_user_value("check_for_updates", true):
|
||||
http_request.request(REMOTE_RELEASES_URL)
|
||||
|
||||
|
||||
#region Signals
|
||||
|
||||
|
||||
func _on_http_request_request_completed(result: int, _response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
|
||||
if result != HTTPRequest.RESULT_SUCCESS: return
|
||||
|
||||
var current_version: String = DMPlugin.get_version()
|
||||
|
||||
# Work out the next version from the releases information on GitHub
|
||||
var response: Variant = JSON.parse_string(body.get_string_from_utf8())
|
||||
if typeof(response) != TYPE_ARRAY: return
|
||||
|
||||
# GitHub releases are in order of creation, not order of version
|
||||
var versions: Array = (response as Array).filter(func(release: Dictionary) -> bool:
|
||||
var version: String = release.tag_name.substr(1)
|
||||
var major_version: int = version.split(".")[0].to_int()
|
||||
var current_major_version: int = current_version.split(".")[0].to_int()
|
||||
return major_version == current_major_version and version_to_number(version) > version_to_number(current_version)
|
||||
)
|
||||
if versions.size() > 0:
|
||||
download_update_panel.next_version_release = versions[0]
|
||||
text = DMConstants.translate(&"update.available").format({ version = versions[0].tag_name.substr(1) })
|
||||
show()
|
||||
|
||||
|
||||
func _on_update_button_pressed() -> void:
|
||||
if needs_reload:
|
||||
var will_refresh: bool = on_before_refresh.call()
|
||||
if will_refresh:
|
||||
EditorInterface.restart_editor(true)
|
||||
else:
|
||||
var dialog_scale: float = EditorInterface.get_editor_scale()
|
||||
download_dialog.min_size = Vector2(300, 250) * dialog_scale
|
||||
download_dialog.popup_centered()
|
||||
|
||||
|
||||
func _on_download_dialog_close_requested() -> void:
|
||||
download_dialog.hide()
|
||||
|
||||
|
||||
func _on_download_update_panel_updated(_updated_to_version: String) -> void:
|
||||
download_dialog.hide()
|
||||
|
||||
needs_reload_dialog.dialog_text = DMConstants.translate(&"update.needs_reload")
|
||||
needs_reload_dialog.ok_button_text = DMConstants.translate(&"update.reload_ok_button")
|
||||
needs_reload_dialog.cancel_button_text = DMConstants.translate(&"update.reload_cancel_button")
|
||||
needs_reload_dialog.popup_centered()
|
||||
|
||||
needs_reload = true
|
||||
text = DMConstants.translate(&"update.reload_project")
|
||||
apply_theme()
|
||||
|
||||
|
||||
func _on_download_update_panel_failed() -> void:
|
||||
download_dialog.hide()
|
||||
update_failed_dialog.dialog_text = DMConstants.translate(&"update.failed")
|
||||
update_failed_dialog.popup_centered()
|
||||
|
||||
|
||||
func _on_needs_reload_dialog_confirmed() -> void:
|
||||
EditorInterface.restart_editor(true)
|
||||
|
||||
|
||||
func _on_timer_timeout() -> void:
|
||||
if not needs_reload:
|
||||
check_for_update()
|
||||
|
||||
|
||||
#endregion
|
||||
@@ -0,0 +1 @@
|
||||
uid://cr1tt12dh5ecr
|
||||
@@ -0,0 +1,42 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://co8yl23idiwbi"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://cr1tt12dh5ecr" path="res://addons/dialogue_manager/components/update_button.gd" id="1_d2tpb"]
|
||||
[ext_resource type="PackedScene" uid="uid://qdxrxv3c3hxk" path="res://addons/dialogue_manager/components/download_update_panel.tscn" id="2_iwm7r"]
|
||||
|
||||
[node name="UpdateButton" type="Button"]
|
||||
visible = false
|
||||
offset_right = 8.0
|
||||
offset_bottom = 8.0
|
||||
theme_override_colors/font_color = Color(0, 0, 0, 1)
|
||||
theme_override_colors/font_hover_color = Color(0, 0, 0, 1)
|
||||
theme_override_colors/font_focus_color = Color(0, 0, 0, 1)
|
||||
text = "v2.9.0 available"
|
||||
flat = true
|
||||
script = ExtResource("1_d2tpb")
|
||||
|
||||
[node name="HTTPRequest" type="HTTPRequest" parent="."]
|
||||
|
||||
[node name="DownloadDialog" type="AcceptDialog" parent="."]
|
||||
title = "Download update"
|
||||
size = Vector2i(400, 300)
|
||||
unresizable = true
|
||||
min_size = Vector2i(300, 250)
|
||||
ok_button_text = "Close"
|
||||
|
||||
[node name="DownloadUpdatePanel" parent="DownloadDialog" instance=ExtResource("2_iwm7r")]
|
||||
|
||||
[node name="UpdateFailedDialog" type="AcceptDialog" parent="."]
|
||||
dialog_text = "You have been updated to version 2.4.3"
|
||||
|
||||
[node name="NeedsReloadDialog" type="ConfirmationDialog" parent="."]
|
||||
|
||||
[node name="Timer" type="Timer" parent="."]
|
||||
wait_time = 14400.0
|
||||
|
||||
[connection signal="pressed" from="." to="." method="_on_update_button_pressed"]
|
||||
[connection signal="request_completed" from="HTTPRequest" to="." method="_on_http_request_request_completed"]
|
||||
[connection signal="close_requested" from="DownloadDialog" to="." method="_on_download_dialog_close_requested"]
|
||||
[connection signal="failed" from="DownloadDialog/DownloadUpdatePanel" to="." method="_on_download_update_panel_failed"]
|
||||
[connection signal="updated" from="DownloadDialog/DownloadUpdatePanel" to="." method="_on_download_update_panel_updated"]
|
||||
[connection signal="confirmed" from="NeedsReloadDialog" to="." method="_on_needs_reload_dialog_confirmed"]
|
||||
[connection signal="timeout" from="Timer" to="." method="_on_timer_timeout"]
|
||||
@@ -0,0 +1,235 @@
|
||||
class_name DMConstants extends RefCounted
|
||||
|
||||
|
||||
const USER_CONFIG_PATH: String = "user://dialogue_manager_user_config.json"
|
||||
|
||||
|
||||
enum MutationBehaviour {
|
||||
Wait,
|
||||
DoNotWait,
|
||||
Skip
|
||||
}
|
||||
|
||||
# Token types
|
||||
|
||||
const TOKEN_FUNCTION: StringName = &"function"
|
||||
const TOKEN_DICTIONARY_REFERENCE: StringName = &"dictionary_reference"
|
||||
const TOKEN_DICTIONARY_NESTED_REFERENCE: StringName = &"dictionary_nested_reference"
|
||||
const TOKEN_GROUP: StringName = &"group"
|
||||
const TOKEN_ARRAY: StringName = &"array"
|
||||
const TOKEN_DICTIONARY: StringName = &"dictionary"
|
||||
const TOKEN_PARENS_OPEN: StringName = &"parens_open"
|
||||
const TOKEN_PARENS_CLOSE: StringName = &"parens_close"
|
||||
const TOKEN_BRACKET_OPEN: StringName = &"bracket_open"
|
||||
const TOKEN_BRACKET_CLOSE: StringName = &"bracket_close"
|
||||
const TOKEN_BRACE_OPEN: StringName = &"brace_open"
|
||||
const TOKEN_BRACE_CLOSE: StringName = &"brace_close"
|
||||
const TOKEN_COLON: StringName = &"colon"
|
||||
const TOKEN_COMPARISON: StringName = &"comparison"
|
||||
const TOKEN_ASSIGNMENT: StringName = &"assignment"
|
||||
const TOKEN_OPERATOR: StringName = &"operator"
|
||||
const TOKEN_COMMA: StringName = &"comma"
|
||||
const TOKEN_NULL_COALESCE: StringName = &"null_coalesce"
|
||||
const TOKEN_DOT: StringName = &"dot"
|
||||
const TOKEN_CONDITION: StringName = &"condition"
|
||||
const TOKEN_BOOL: StringName = &"bool"
|
||||
const TOKEN_NOT: StringName = &"not"
|
||||
const TOKEN_AND_OR: StringName = &"and_or"
|
||||
const TOKEN_STRING: StringName = &"string"
|
||||
const TOKEN_NUMBER: StringName = &"number"
|
||||
const TOKEN_VARIABLE: StringName = &"variable"
|
||||
const TOKEN_COMMENT: StringName = &"comment"
|
||||
|
||||
const TOKEN_VALUE: StringName = &"value"
|
||||
const TOKEN_ERROR: StringName = &"error"
|
||||
|
||||
# Line types
|
||||
|
||||
const TYPE_UNKNOWN: StringName = &""
|
||||
const TYPE_IMPORT: StringName = &"import"
|
||||
const TYPE_USING: StringName = &"using"
|
||||
const TYPE_COMMENT: StringName = &"comment"
|
||||
const TYPE_RESPONSE: StringName = &"response"
|
||||
const TYPE_CUE: StringName = &"cue"
|
||||
const TYPE_CONDITION: StringName = &"condition"
|
||||
const TYPE_WHILE: StringName = &"while"
|
||||
const TYPE_MATCH: StringName = &"match"
|
||||
const TYPE_WHEN: StringName = &"when"
|
||||
const TYPE_MUTATION: StringName = &"mutation"
|
||||
const TYPE_GOTO: StringName = &"goto"
|
||||
const TYPE_DIALOGUE: StringName = &"dialogue"
|
||||
const TYPE_RANDOM: StringName = &"random"
|
||||
const TYPE_ERROR: StringName = &"error"
|
||||
|
||||
# Line IDs
|
||||
|
||||
const ID_NULL: StringName = &""
|
||||
const ID_ERROR: StringName = &"error"
|
||||
const ID_END: StringName = &"end"
|
||||
const ID_END_CONVERSATION: StringName = &"end!"
|
||||
|
||||
# Errors
|
||||
|
||||
const ERR_ERRORS_IN_IMPORTED_FILE: int = 100
|
||||
const ERR_FILE_ALREADY_IMPORTED: int = 101
|
||||
const ERR_DUPLICATE_IMPORT_NAME: int = 102
|
||||
const ERR_EMPTY_CUE: int = 103
|
||||
const ERR_DUPLICATE_CUE: int = 104
|
||||
const ERR_CUE_INVALID_CHARACTERS: int = 106
|
||||
const ERR_UNKNOWN_CUE: int = 107
|
||||
const ERR_INVALID_CUE_REFERENCE: int= 108
|
||||
const ERR_CUE_REFERENCE_HAS_NO_CONTENT: int = 109
|
||||
const ERR_INVALID_EXPRESSION: int = 110
|
||||
const ERR_UNEXPECTED_CONDITION: int = 111
|
||||
const ERR_DUPLICATE_ID: int = 112
|
||||
const ERR_MISSING_ID: int = 113
|
||||
const ERR_INVALID_INDENTATION: int = 114
|
||||
const ERR_INVALID_CONDITION_INDENTATION: int = 115
|
||||
const ERR_INCOMPLETE_EXPRESSION: int = 116
|
||||
const ERR_INVALID_EXPRESSION_FOR_VALUE: int = 117
|
||||
const ERR_UNKNOWN_ERROR: int = 118
|
||||
const ERR_CUE_BEGINS_WITH_NUMBER: int = 119
|
||||
const ERR_UNEXPECTED_END_OF_EXPRESSION: int = 120
|
||||
const ERR_UNEXPECTED_FUNCTION: int = 121
|
||||
const ERR_UNEXPECTED_BRACKET: int = 122
|
||||
const ERR_UNEXPECTED_CLOSING_BRACKET: int = 123
|
||||
const ERR_MISSING_CLOSING_BRACKET: int = 124
|
||||
const ERR_UNEXPECTED_OPERATOR: int = 125
|
||||
const ERR_UNEXPECTED_COMMA: int = 126
|
||||
const ERR_UNEXPECTED_COLON: int = 127
|
||||
const ERR_UNEXPECTED_DOT: int = 128
|
||||
const ERR_UNEXPECTED_BOOLEAN: int = 129
|
||||
const ERR_UNEXPECTED_STRING: int = 130
|
||||
const ERR_UNEXPECTED_NUMBER: int = 131
|
||||
const ERR_UNEXPECTED_VARIABLE: int = 132
|
||||
const ERR_INVALID_INDEX: int = 133
|
||||
const ERR_UNEXPECTED_ASSIGNMENT: int = 134
|
||||
const ERR_UNKNOWN_USING: int = 135
|
||||
const ERR_EXPECTED_WHEN_OR_ELSE: int = 136
|
||||
const ERR_ONLY_ONE_ELSE_ALLOWED: int = 137
|
||||
const ERR_WHEN_MUST_BELONG_TO_MATCH: int = 138
|
||||
const ERR_CONCURRENT_LINE_WITHOUT_ORIGIN: int = 139
|
||||
const ERR_GOTO_NOT_ALLOWED_ON_CONCURRECT_LINES: int = 140
|
||||
const ERR_UNEXPECTED_SYNTAX_ON_NESTED_DIALOGUE_LINE: int = 141
|
||||
const ERR_NESTED_DIALOGUE_INVALID_JUMP: int = 142
|
||||
const ERR_MISSING_RESOURCE_FOR_AUTOSTART: int = 143
|
||||
const ERR_LONELY_STATIC_ID: int = 144
|
||||
|
||||
|
||||
static var _current_locale: String = ""
|
||||
static var _current_translation: Translation
|
||||
static var _en_translation: Translation
|
||||
|
||||
|
||||
## Get the error message
|
||||
static func get_error_message(error: int) -> String:
|
||||
match error:
|
||||
ERR_ERRORS_IN_IMPORTED_FILE:
|
||||
return translate(&"errors.import_errors")
|
||||
ERR_FILE_ALREADY_IMPORTED:
|
||||
return translate(&"errors.already_imported")
|
||||
ERR_DUPLICATE_IMPORT_NAME:
|
||||
return translate(&"errors.duplicate_import")
|
||||
ERR_EMPTY_CUE:
|
||||
return translate(&"errors.empty_cue")
|
||||
ERR_DUPLICATE_CUE:
|
||||
return translate(&"errors.duplicate_cue")
|
||||
ERR_CUE_INVALID_CHARACTERS:
|
||||
return translate(&"errors.invalid_cue_string")
|
||||
ERR_CUE_BEGINS_WITH_NUMBER:
|
||||
return translate(&"errors.invalid_cue_number")
|
||||
ERR_UNKNOWN_CUE:
|
||||
return translate(&"errors.unknown_cue")
|
||||
ERR_INVALID_CUE_REFERENCE:
|
||||
return translate(&"errors.jump_to_invalid_cue")
|
||||
ERR_CUE_REFERENCE_HAS_NO_CONTENT:
|
||||
return translate(&"errors.cue_has_no_content")
|
||||
ERR_INVALID_EXPRESSION:
|
||||
return translate(&"errors.invalid_expression")
|
||||
ERR_UNEXPECTED_CONDITION:
|
||||
return translate(&"errors.unexpected_condition")
|
||||
ERR_DUPLICATE_ID:
|
||||
return translate(&"errors.duplicate_id")
|
||||
ERR_MISSING_ID:
|
||||
return translate(&"errors.missing_id")
|
||||
ERR_INVALID_INDENTATION:
|
||||
return translate(&"errors.invalid_indentation")
|
||||
ERR_INVALID_CONDITION_INDENTATION:
|
||||
return translate(&"errors.condition_has_no_content")
|
||||
ERR_INCOMPLETE_EXPRESSION:
|
||||
return translate(&"errors.incomplete_expression")
|
||||
ERR_INVALID_EXPRESSION_FOR_VALUE:
|
||||
return translate(&"errors.invalid_expression_for_value")
|
||||
ERR_FILE_NOT_FOUND:
|
||||
return translate(&"errors.file_not_found")
|
||||
ERR_UNEXPECTED_END_OF_EXPRESSION:
|
||||
return translate(&"errors.unexpected_end_of_expression")
|
||||
ERR_UNEXPECTED_FUNCTION:
|
||||
return translate(&"errors.unexpected_function")
|
||||
ERR_UNEXPECTED_BRACKET:
|
||||
return translate(&"errors.unexpected_bracket")
|
||||
ERR_UNEXPECTED_CLOSING_BRACKET:
|
||||
return translate(&"errors.unexpected_closing_bracket")
|
||||
ERR_MISSING_CLOSING_BRACKET:
|
||||
return translate(&"errors.missing_closing_bracket")
|
||||
ERR_UNEXPECTED_OPERATOR:
|
||||
return translate(&"errors.unexpected_operator")
|
||||
ERR_UNEXPECTED_COMMA:
|
||||
return translate(&"errors.unexpected_comma")
|
||||
ERR_UNEXPECTED_COLON:
|
||||
return translate(&"errors.unexpected_colon")
|
||||
ERR_UNEXPECTED_DOT:
|
||||
return translate(&"errors.unexpected_dot")
|
||||
ERR_UNEXPECTED_BOOLEAN:
|
||||
return translate(&"errors.unexpected_boolean")
|
||||
ERR_UNEXPECTED_STRING:
|
||||
return translate(&"errors.unexpected_string")
|
||||
ERR_UNEXPECTED_NUMBER:
|
||||
return translate(&"errors.unexpected_number")
|
||||
ERR_UNEXPECTED_VARIABLE:
|
||||
return translate(&"errors.unexpected_variable")
|
||||
ERR_INVALID_INDEX:
|
||||
return translate(&"errors.invalid_index")
|
||||
ERR_UNEXPECTED_ASSIGNMENT:
|
||||
return translate(&"errors.unexpected_assignment")
|
||||
ERR_UNKNOWN_USING:
|
||||
return translate(&"errors.unknown_using")
|
||||
ERR_EXPECTED_WHEN_OR_ELSE:
|
||||
return translate(&"errors.expected_when_or_else")
|
||||
ERR_ONLY_ONE_ELSE_ALLOWED:
|
||||
return translate(&"errors.only_one_else_allowed")
|
||||
ERR_WHEN_MUST_BELONG_TO_MATCH:
|
||||
return translate(&"errors.when_must_belong_to_match")
|
||||
ERR_CONCURRENT_LINE_WITHOUT_ORIGIN:
|
||||
return translate(&"errors.concurrent_line_without_origin")
|
||||
ERR_GOTO_NOT_ALLOWED_ON_CONCURRECT_LINES:
|
||||
return translate(&"errors.goto_not_allowed_on_concurrect_lines")
|
||||
ERR_UNEXPECTED_SYNTAX_ON_NESTED_DIALOGUE_LINE:
|
||||
return translate(&"errors.unexpected_syntax_on_nested_dialogue_line")
|
||||
ERR_NESTED_DIALOGUE_INVALID_JUMP:
|
||||
return translate(&"errors.err_nested_dialogue_invalid_jump")
|
||||
ERR_MISSING_RESOURCE_FOR_AUTOSTART:
|
||||
return translate(&"errors.missing_resource_for_autostart")
|
||||
ERR_LONELY_STATIC_ID:
|
||||
return translate(&"Static IDs can't be on a line with no other content.")
|
||||
|
||||
return translate(&"errors.unknown")
|
||||
|
||||
|
||||
static func translate(string: String) -> String:
|
||||
var locale: String = TranslationServer.get_tool_locale()
|
||||
if _current_translation == null or _current_locale != locale:
|
||||
var base_path: String = new().get_script().resource_path.get_base_dir()
|
||||
var translation_path: String = "%s/l10n/%s.po" % [base_path, locale]
|
||||
var fallback_translation_path: String = "%s/l10n/%s.po" % [base_path, locale.substr(0, 2)]
|
||||
var en_translation_path: String = "%s/l10n/en.po" % base_path
|
||||
_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))
|
||||
_current_locale = locale
|
||||
_en_translation = load(en_translation_path)
|
||||
var message: StringName = _current_translation.get_message(string)
|
||||
if not message.is_empty():
|
||||
return message
|
||||
elif not _en_translation.get_message(string).is_empty():
|
||||
return _en_translation.get_message(string)
|
||||
else:
|
||||
return string
|
||||
@@ -0,0 +1 @@
|
||||
uid://b1oarbmjtyesf
|
||||
@@ -0,0 +1,83 @@
|
||||
@tool
|
||||
|
||||
class_name DMDebuggerPlugin extends EditorDebuggerPlugin
|
||||
|
||||
|
||||
const DebuggerView: PackedScene = preload("./views/debugger_view.tscn")
|
||||
|
||||
|
||||
var debugger_view: DMDebuggerView
|
||||
var debug_info: Dictionary = {}
|
||||
|
||||
|
||||
func _setup_session(session_id: int) -> void:
|
||||
var session: EditorDebuggerSession = get_session(session_id)
|
||||
session.started.connect(_on_started)
|
||||
session.breaked.connect(_on_breaked)
|
||||
session.continued.connect(_on_continued)
|
||||
session.stopped.connect(_on_stopped)
|
||||
|
||||
debugger_view = DebuggerView.instantiate()
|
||||
debugger_view.name = DMConstants.translate("Dialogue")
|
||||
debugger_view.session = session
|
||||
session.add_session_tab(debugger_view)
|
||||
|
||||
|
||||
func _has_capture(capture: String) -> bool:
|
||||
return capture == "dm"
|
||||
|
||||
|
||||
func _capture(message: String, data: Array, _session_id: int) -> bool:
|
||||
match message:
|
||||
"dm:debug":
|
||||
debug_info = data[0]
|
||||
return true
|
||||
|
||||
"dm:current_scene":
|
||||
debugger_view.current_scene = data[0]
|
||||
return true
|
||||
|
||||
"dm:state":
|
||||
debugger_view.contexts = data[0]
|
||||
debugger_view.autoloads = data[1]
|
||||
return true
|
||||
|
||||
"dm:get_line":
|
||||
debugger_view.add_line(data[0])
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
|
||||
#region Signals
|
||||
|
||||
|
||||
func _on_started() -> void:
|
||||
debugger_view.start()
|
||||
debug_info = {}
|
||||
|
||||
|
||||
func _on_breaked(_can_debug: bool) -> void:
|
||||
if debug_info.is_empty(): return
|
||||
|
||||
DMPlugin.open_file_at_line(debug_info.resource_path, debug_info.line_number)
|
||||
|
||||
await (Engine.get_main_loop() as SceneTree).create_timer(1).timeout
|
||||
|
||||
EditorInterface.set_main_screen_editor("Dialogue")
|
||||
DMPlugin.instance.main_view.code_edit.runtime_error = DMError.new({
|
||||
line_number = debug_info.line_number + 1
|
||||
})
|
||||
|
||||
debug_info = {}
|
||||
|
||||
|
||||
func _on_continued() -> void:
|
||||
DMPlugin.instance.main_view.code_edit.runtime_error = null
|
||||
debug_info = {}
|
||||
|
||||
|
||||
func _on_stopped() -> void:
|
||||
debugger_view.stop()
|
||||
DMPlugin.instance.main_view.code_edit.runtime_error = null
|
||||
debug_info = {}
|
||||
@@ -0,0 +1 @@
|
||||
uid://d0s08isf1ex1n
|
||||
@@ -0,0 +1,147 @@
|
||||
## A line of dialogue returned from [code]DialogueManager[/code].
|
||||
class_name DialogueLine extends RefCounted
|
||||
|
||||
|
||||
## The ID of this line
|
||||
var id: String
|
||||
|
||||
## The internal type of this dialogue object. One of [code]TYPE_DIALOGUE[/code] or [code]TYPE_MUTATION[/code]
|
||||
var type: String = DMConstants.TYPE_DIALOGUE
|
||||
|
||||
## The next line ID after this line.
|
||||
var next_id: String = ""
|
||||
|
||||
## The character name that is saying this line.
|
||||
var character: String = ""
|
||||
|
||||
## A dictionary of variable replacements fo the character name. Generally for internal use only.
|
||||
var character_replacements: Array[Dictionary] = []
|
||||
|
||||
## The dialogue being spoken.
|
||||
var text: String = ""
|
||||
|
||||
## A dictionary of replacements for the text. Generally for internal use only.
|
||||
var text_replacements: Array[Dictionary] = []
|
||||
|
||||
## The key to use for translating this line.
|
||||
var static_id: String = ""
|
||||
|
||||
## A map for speed changes when typing out the dialogue text.
|
||||
var speeds: Dictionary = {}
|
||||
|
||||
## A map of any mutations to run while typing out the dialogue text.
|
||||
var inline_mutations: Array[Array] = []
|
||||
|
||||
## A list of responses attached to this line of dialogue.
|
||||
var responses: Array = []
|
||||
|
||||
## A list of lines that are spoken simultaneously with this one.
|
||||
var concurrent_lines: Array[DialogueLine] = []
|
||||
|
||||
## A list of any extra game states to check when resolving variables and mutations.
|
||||
var extra_game_states: Array = []
|
||||
|
||||
## How long to show this line before advancing to the next. Either a float of seconds (as a string), [code]"auto"[/code], or empty string.
|
||||
var time: String = ""
|
||||
|
||||
## Any #tags that were included in the line
|
||||
var tags: PackedStringArray = []
|
||||
|
||||
## The mutation details if this is a mutation line (where [code]type == TYPE_MUTATION[/code]).
|
||||
var mutation: Dictionary = {}
|
||||
|
||||
|
||||
func _init(data: Dictionary = {}) -> void:
|
||||
if data.size() > 0:
|
||||
id = data.id
|
||||
next_id = data.next_id
|
||||
type = data.type
|
||||
extra_game_states = data.get("extra_game_states", [])
|
||||
|
||||
match type:
|
||||
DMConstants.TYPE_DIALOGUE:
|
||||
character = data.character
|
||||
character_replacements = data.get("character_replacements", [] as Array[Dictionary])
|
||||
text = data.text
|
||||
text_replacements = data.get("text_replacements", [] as Array[Dictionary])
|
||||
static_id = data.get("static_id", data.text)
|
||||
speeds = data.get("speeds", {})
|
||||
inline_mutations = data.get("inline_mutations", [] as Array[Array])
|
||||
time = data.get("time", "")
|
||||
tags = data.get("tags", [])
|
||||
concurrent_lines = data.get("concurrent_lines", [] as Array[DialogueLine])
|
||||
|
||||
DMConstants.TYPE_MUTATION:
|
||||
mutation = data.mutation
|
||||
|
||||
|
||||
## Reload this line from it's resource.
|
||||
func refresh() -> void:
|
||||
if not "@" in id:
|
||||
push_warning(DMConstants.translate("Cannot refresh dialogue line because its ID is missing a resource UID."))
|
||||
return
|
||||
|
||||
var resource: DialogueResource = load("uid://%s" % id.split("@")[0])
|
||||
var next_dialogue_line: DialogueLine = await resource.get_next_dialogue_line(next_id, extra_game_states)
|
||||
type = next_dialogue_line.type
|
||||
next_id = next_dialogue_line.next_id
|
||||
character = next_dialogue_line.character
|
||||
character_replacements = next_dialogue_line.character_replacements
|
||||
text = next_dialogue_line.text
|
||||
text_replacements = next_dialogue_line.text_replacements
|
||||
static_id = next_dialogue_line.static_id
|
||||
speeds = next_dialogue_line.speeds
|
||||
inline_mutations = next_dialogue_line.inline_mutations
|
||||
responses = next_dialogue_line.responses
|
||||
concurrent_lines = next_dialogue_line.concurrent_lines
|
||||
extra_game_states = next_dialogue_line.extra_game_states
|
||||
time = next_dialogue_line.time
|
||||
tags = next_dialogue_line.tags
|
||||
mutation = next_dialogue_line.mutation
|
||||
|
||||
|
||||
## Restore a [DialogueLine] from a [code]to_serialized[/code] string, providing any extra
|
||||
## game states that would have been used fetch the line in the first place.
|
||||
static func new_from_serialized(serialized_data: String, extra_game_states_: Array = []) -> DialogueLine:
|
||||
var bits: PackedStringArray = serialized_data.split("=>")
|
||||
var id_bits: PackedStringArray = bits[0].split("@")
|
||||
var resource: DialogueResource = load("uid://%s" % id_bits[0])
|
||||
var line: DialogueLine = await resource.get_next_dialogue_line(id_bits[1], extra_game_states_)
|
||||
line.next_id = bits[1]
|
||||
return line
|
||||
|
||||
|
||||
## Serialize this [DialogueLine] to a string. Restore with
|
||||
## [code]DialogueLine.new_from_serialized(...)[/code].
|
||||
func to_serialized() -> String:
|
||||
return "=>".join([id, next_id])
|
||||
|
||||
|
||||
func _to_string() -> String:
|
||||
match type:
|
||||
DMConstants.TYPE_DIALOGUE:
|
||||
return "<DialogueLine character=\"%s\" text=\"%s\">" % [character, text]
|
||||
DMConstants.TYPE_MUTATION:
|
||||
return "<DialogueLine mutation>"
|
||||
return ""
|
||||
|
||||
|
||||
## Check if a dialogue line has a given tag.
|
||||
func has_tag(tag_name: String) -> bool:
|
||||
if tags.has(tag_name):
|
||||
return true
|
||||
else:
|
||||
var wrapped: String = "%s=" % tag_name
|
||||
for t: String in tags:
|
||||
if t.begins_with(wrapped):
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
## Get the value of a tag if the tag is in the form of [code]tag=value[/code]
|
||||
func get_tag_value(tag_name: String) -> String:
|
||||
var wrapped: String = "%s=" % tag_name
|
||||
for t: String in tags:
|
||||
if t.begins_with(wrapped):
|
||||
return t.replace(wrapped, "").strip_edges()
|
||||
return ""
|
||||
@@ -0,0 +1 @@
|
||||
uid://rhuq0eyf8ar2
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
uid://c3rodes2l3gxb
|
||||
@@ -0,0 +1,11 @@
|
||||
class_name DMDialogueProcessor extends RefCounted
|
||||
|
||||
|
||||
## Override to modify the incoming raw string.
|
||||
func _preprocess_line(raw_line: String) -> String:
|
||||
return raw_line
|
||||
|
||||
|
||||
## Override to modify the outgoing dialogue line.
|
||||
func _process_line(_line: DMCompiledLine) -> void:
|
||||
pass
|
||||
@@ -0,0 +1 @@
|
||||
uid://m3b28rmso14t
|
||||
@@ -0,0 +1,40 @@
|
||||
@tool
|
||||
@icon("./assets/icon.svg")
|
||||
|
||||
## A collection of dialogue lines for use with [code]DialogueManager[/code].
|
||||
class_name DialogueResource extends Resource
|
||||
|
||||
|
||||
## A list of state shortcuts
|
||||
@export var using_states: PackedStringArray = []
|
||||
|
||||
## A map of cues and the lines they point to.
|
||||
@export var cues: Dictionary = {}
|
||||
|
||||
## A list of character names.
|
||||
@export var character_names: PackedStringArray = []
|
||||
|
||||
## The first cue in the file.
|
||||
@export var first_cue: String = ""
|
||||
|
||||
## A map of the encoded lines of dialogue.
|
||||
@export var lines: Dictionary = {}
|
||||
|
||||
|
||||
## Get the next printable line of dialogue, starting from a referenced line ([code]cue[/code] can
|
||||
## be a cues string or a stringified line number). Runs any mutations along the way and then returns
|
||||
## the first dialogue line encountered.
|
||||
func get_next_dialogue_line(cue: String = "", extra_game_states: Array = [], mutation_behaviour: DMConstants.MutationBehaviour = DMConstants.MutationBehaviour.Wait) -> DialogueLine:
|
||||
return await Engine.get_singleton("DialogueManager").get_next_dialogue_line(self, cue, extra_game_states, mutation_behaviour)
|
||||
|
||||
|
||||
## Get the list of any cues found in the file.
|
||||
func get_cues() -> PackedStringArray:
|
||||
return cues.keys()
|
||||
|
||||
|
||||
func _to_string() -> String:
|
||||
if resource_path:
|
||||
return "<DialogueResource path=\"%s\">" % [resource_path]
|
||||
else:
|
||||
return "<DialogueResource>"
|
||||
@@ -0,0 +1 @@
|
||||
uid://dbs4435dsf3ry
|
||||
@@ -0,0 +1,76 @@
|
||||
## A response to a line of dialogue, usualy attached to a [code]DialogueLine[/code].
|
||||
class_name DialogueResponse extends RefCounted
|
||||
|
||||
|
||||
## The ID of this response
|
||||
var id: String
|
||||
|
||||
## The internal type of this dialogue object, always set to [code]TYPE_RESPONSE[/code].
|
||||
var type: String = DMConstants.TYPE_RESPONSE
|
||||
|
||||
## The next line ID to use if this response is selected by the player.
|
||||
var next_id: String = ""
|
||||
|
||||
## [code]true[/code] if the condition of this line was met.
|
||||
var is_allowed: bool = true
|
||||
|
||||
## The original condition text.
|
||||
var condition_as_text: String = ""
|
||||
|
||||
## A character (depending on the "characters in responses" behaviour setting).
|
||||
var character: String = ""
|
||||
|
||||
## A dictionary of varialbe replaces for the character name. Generally for internal use only.
|
||||
var character_replacements: Array[Dictionary] = []
|
||||
|
||||
## The prompt for this response.
|
||||
var text: String = ""
|
||||
|
||||
## A dictionary of variable replaces for the text. Generally for internal use only.
|
||||
var text_replacements: Array[Dictionary] = []
|
||||
|
||||
## Any #tags
|
||||
var tags: PackedStringArray = []
|
||||
|
||||
## The key to use for translating the text.
|
||||
var static_id: String = ""
|
||||
|
||||
|
||||
func _init(data: Dictionary = {}) -> void:
|
||||
if data.size() > 0:
|
||||
id = data.id
|
||||
type = data.type
|
||||
next_id = data.next_id
|
||||
is_allowed = data.is_allowed
|
||||
character = data.character
|
||||
character_replacements = data.character_replacements
|
||||
text = data.text
|
||||
text_replacements = data.text_replacements
|
||||
tags = data.tags
|
||||
static_id = data.static_id
|
||||
condition_as_text = data.condition_as_text
|
||||
|
||||
|
||||
func _to_string() -> String:
|
||||
return "<DialogueResponse text=\"%s\">" % text
|
||||
|
||||
|
||||
## Check if a dialogue line has a given tag.
|
||||
func has_tag(tag_name: String) -> bool:
|
||||
if tags.has(tag_name):
|
||||
return true
|
||||
else:
|
||||
var wrapped: String = "%s=" % tag_name
|
||||
for t: String in tags:
|
||||
if t.begins_with(wrapped):
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
## Get the value of a tag if the tag is in the form of [code]tag=value[/code]
|
||||
func get_tag_value(tag_name: String) -> String:
|
||||
var wrapped: String = "%s=" % tag_name
|
||||
for t: String in tags:
|
||||
if t.begins_with(wrapped):
|
||||
return t.replace(wrapped, "").strip_edges()
|
||||
return ""
|
||||
@@ -0,0 +1 @@
|
||||
uid://cm0xpfeywpqid
|
||||
@@ -0,0 +1,72 @@
|
||||
class_name DMTranslationParserPlugin extends EditorTranslationParserPlugin
|
||||
|
||||
|
||||
## Cached result of parsing a dialogue file.
|
||||
var data: DMCompilerResult
|
||||
## List of characters that were added.
|
||||
var translated_character_names: PackedStringArray = []
|
||||
var translated_lines: Array[Dictionary] = []
|
||||
|
||||
|
||||
func _parse_file(path: String) -> Array[PackedStringArray]:
|
||||
var msgs: Array[PackedStringArray] = []
|
||||
var file: FileAccess = FileAccess.open(path, FileAccess.READ)
|
||||
var text: String = file.get_as_text()
|
||||
|
||||
data = DMCompiler.compile_string(text, path)
|
||||
|
||||
var known_keys: PackedStringArray = PackedStringArray([])
|
||||
|
||||
# Add all character names if settings ask for it
|
||||
if DMSettings.get_setting(DMSettings.INCLUDE_CHARACTERS_IN_TRANSLATABLE_STRINGS_LIST, true):
|
||||
translated_character_names = [] as Array[DialogueLine]
|
||||
for character_name: String in data.character_names:
|
||||
if character_name in known_keys: continue
|
||||
|
||||
known_keys.append(character_name)
|
||||
|
||||
translated_character_names.append(character_name)
|
||||
msgs.append(PackedStringArray([character_name.replace('"', '\"'), "dialogue", "", DMConstants.translate("translation_plugin.character_name")]))
|
||||
|
||||
# Add all dialogue lines and responses
|
||||
var dialogue: Dictionary = data.lines
|
||||
for key: String in dialogue.keys():
|
||||
var line: Dictionary = dialogue.get(key)
|
||||
|
||||
if not line.type in [DMConstants.TYPE_DIALOGUE, DMConstants.TYPE_RESPONSE]: continue
|
||||
|
||||
var static_id: String = line.get(&"static_id", line.text)
|
||||
if static_id.is_empty():
|
||||
static_id = line.text
|
||||
|
||||
if static_id in known_keys: continue
|
||||
|
||||
known_keys.append(static_id)
|
||||
translated_lines.append(line)
|
||||
|
||||
var has_static_id: bool = static_id != "" and static_id != line.text
|
||||
|
||||
var message: String = static_id.replace('"', '\"') if has_static_id else line.text
|
||||
var context: String = "dialogue"
|
||||
var plural: String = ""
|
||||
var extra_details: PackedStringArray = []
|
||||
if line.has("character"):
|
||||
extra_details.append("Character: %s" % line.get("character", ""))
|
||||
if has_static_id and not line.text.is_empty():
|
||||
extra_details.append("Line: %s" % line.text)
|
||||
if line.has("notes"):
|
||||
extra_details.append("Notes: %s" % line.get("notes", ""))
|
||||
var notes: String = "\n".join(extra_details)
|
||||
msgs.append(PackedStringArray([
|
||||
message,
|
||||
context,
|
||||
plural,
|
||||
notes,
|
||||
key
|
||||
]))
|
||||
|
||||
return msgs
|
||||
|
||||
|
||||
func _get_recognized_extensions() -> PackedStringArray:
|
||||
return ["dialogue"]
|
||||
@@ -0,0 +1 @@
|
||||
uid://c6bya881h1egb
|
||||
@@ -0,0 +1,278 @@
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
|
||||
namespace DialogueManagerRuntime
|
||||
{
|
||||
public partial class ExampleBalloon : CanvasLayer
|
||||
{
|
||||
[Export] public Resource DialogueResource;
|
||||
[Export] public string StartFromLabel = "";
|
||||
[Export] public bool AutoStart = false;
|
||||
[Export] public string NextAction = "ui_accept";
|
||||
[Export] public string SkipAction = "ui_cancel";
|
||||
|
||||
|
||||
Control balloon;
|
||||
RichTextLabel characterLabel;
|
||||
DialogueLabel dialogueLabel;
|
||||
DialogueResponsesMenu responsesMenu;
|
||||
Polygon2D progress;
|
||||
|
||||
Array<Variant> temporaryGameStates = new Array<Variant>();
|
||||
bool isWaitingForInput = false;
|
||||
bool willHideBalloon = false;
|
||||
|
||||
DialogueLine dialogueLine;
|
||||
DialogueLine DialogueLine
|
||||
{
|
||||
get => dialogueLine;
|
||||
set
|
||||
{
|
||||
// Dialogue has finished so close the balloon
|
||||
if (value == null)
|
||||
{
|
||||
if (Owner == null)
|
||||
{
|
||||
QueueFree();
|
||||
}
|
||||
else
|
||||
{
|
||||
Hide();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
dialogueLine = value;
|
||||
ApplyDialogueLine();
|
||||
}
|
||||
}
|
||||
|
||||
Timer MutationCooldown = new Timer();
|
||||
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
balloon = GetNode<Control>("%Balloon");
|
||||
characterLabel = GetNode<RichTextLabel>("%CharacterLabel");
|
||||
dialogueLabel = GetNode<DialogueLabel>("%DialogueLabel");
|
||||
responsesMenu = GetNode<DialogueResponsesMenu>("%ResponsesMenu");
|
||||
progress = GetNode<Polygon2D>("%Progress");
|
||||
|
||||
balloon.Hide();
|
||||
|
||||
balloon.GuiInput += (@event) =>
|
||||
{
|
||||
if (dialogueLabel.IsTyping)
|
||||
{
|
||||
bool mouseWasClicked = @event is InputEventMouseButton && (@event as InputEventMouseButton).ButtonIndex == MouseButton.Left && @event.IsPressed();
|
||||
bool skipButtonWasPressed = @event.IsActionPressed(SkipAction);
|
||||
if (mouseWasClicked || skipButtonWasPressed)
|
||||
{
|
||||
GetViewport().SetInputAsHandled();
|
||||
dialogueLabel.SkipTyping();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isWaitingForInput) return;
|
||||
if (dialogueLine.Responses.Count > 0) return;
|
||||
|
||||
GetViewport().SetInputAsHandled();
|
||||
|
||||
if (@event is InputEventMouseButton && @event.IsPressed() && (@event as InputEventMouseButton).ButtonIndex == MouseButton.Left)
|
||||
{
|
||||
Next(dialogueLine.NextId);
|
||||
}
|
||||
else if (@event.IsActionPressed(NextAction) && GetViewport().GuiGetFocusOwner() == balloon)
|
||||
{
|
||||
Next(dialogueLine.NextId);
|
||||
}
|
||||
};
|
||||
|
||||
if (string.IsNullOrEmpty(responsesMenu.NextAction))
|
||||
{
|
||||
responsesMenu.NextAction = NextAction;
|
||||
}
|
||||
responsesMenu.ResponseSelected += (response) =>
|
||||
{
|
||||
Next(response.NextId);
|
||||
};
|
||||
|
||||
|
||||
// Hide the balloon when a mutation is running
|
||||
MutationCooldown.Timeout += () =>
|
||||
{
|
||||
if (willHideBalloon)
|
||||
{
|
||||
willHideBalloon = false;
|
||||
balloon.Hide();
|
||||
}
|
||||
};
|
||||
AddChild(MutationCooldown);
|
||||
|
||||
DialogueManager.Mutated += OnMutated;
|
||||
|
||||
if (AutoStart)
|
||||
{
|
||||
if (!IsInstanceValid(DialogueResource))
|
||||
{
|
||||
throw new System.Exception(DialogueManager.GetErrorMessage(143));
|
||||
}
|
||||
Start();
|
||||
}
|
||||
|
||||
// EXAMPLE MESSAGE
|
||||
var warning = new Button
|
||||
{
|
||||
Text = DialogueManager.Translate("This is an example balloon. Create your own balloon in 'Project > Tools > Dialogue > Create Balloon...'"),
|
||||
Disabled = true
|
||||
};
|
||||
warning.SetAnchorsAndOffsetsPreset(Control.LayoutPreset.TopWide);
|
||||
balloon.AddChild(warning);
|
||||
balloon.MoveChild(warning, 0);
|
||||
// END EXAMPLE MESSAGE
|
||||
}
|
||||
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
DialogueManager.Mutated -= OnMutated;
|
||||
}
|
||||
|
||||
|
||||
public override void _UnhandledInput(InputEvent @event)
|
||||
{
|
||||
// Only the balloon is allowed to handle input while it's showing
|
||||
GetViewport().SetInputAsHandled();
|
||||
}
|
||||
|
||||
|
||||
public override async void _Notification(int what)
|
||||
{
|
||||
// Detect a change of locale and update the current dialogue line to show the new language
|
||||
if (what == NotificationTranslationChanged && IsInstanceValid(dialogueLabel))
|
||||
{
|
||||
float visibleRatio = dialogueLabel.VisibleRatio;
|
||||
DialogueLine = await DialogueManager.GetNextDialogueLine(DialogueResource, DialogueLine.Id, temporaryGameStates);
|
||||
if (visibleRatio < 1.0f)
|
||||
{
|
||||
dialogueLabel.SkipTyping();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
base._Process(delta);
|
||||
|
||||
if (IsInstanceValid(dialogueLine))
|
||||
{
|
||||
progress.Visible = !dialogueLabel.IsTyping && dialogueLine.Responses.Count == 0 && !dialogueLine.HasTag("voice");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async void Start(Resource dialogueResource = null, string label = "", Array<Variant> extraGameStates = null)
|
||||
{
|
||||
temporaryGameStates = new Array<Variant> { this } + (extraGameStates ?? new Array<Variant>());
|
||||
isWaitingForInput = false;
|
||||
|
||||
if (IsInstanceValid(dialogueResource))
|
||||
{
|
||||
DialogueResource = dialogueResource;
|
||||
}
|
||||
if (label != "")
|
||||
{
|
||||
StartFromLabel = label;
|
||||
}
|
||||
|
||||
DialogueLine = await DialogueManager.GetNextDialogueLine(DialogueResource, StartFromLabel, temporaryGameStates);
|
||||
Show();
|
||||
}
|
||||
|
||||
|
||||
public async void Next(string nextId)
|
||||
{
|
||||
DialogueLine = await DialogueManager.GetNextDialogueLine(DialogueResource, nextId, temporaryGameStates);
|
||||
}
|
||||
|
||||
|
||||
#region Helpers
|
||||
|
||||
|
||||
private async void ApplyDialogueLine()
|
||||
{
|
||||
MutationCooldown.Stop();
|
||||
|
||||
isWaitingForInput = false;
|
||||
balloon.FocusMode = Control.FocusModeEnum.All;
|
||||
balloon.GrabFocus();
|
||||
|
||||
// Set up the character name
|
||||
characterLabel.Visible = !string.IsNullOrEmpty(dialogueLine.Character);
|
||||
characterLabel.Text = Tr(dialogueLine.Character, "dialogue");
|
||||
|
||||
// Set up the dialogue
|
||||
dialogueLabel.Hide();
|
||||
dialogueLabel.DialogueLine = dialogueLine;
|
||||
|
||||
// Set up the responses
|
||||
responsesMenu.Hide();
|
||||
responsesMenu.Responses = dialogueLine.Responses;
|
||||
|
||||
// Type out the text
|
||||
balloon.Show();
|
||||
willHideBalloon = false;
|
||||
dialogueLabel.Show();
|
||||
if (!string.IsNullOrEmpty(dialogueLine.Text))
|
||||
{
|
||||
dialogueLabel.TypeOut();
|
||||
await ToSignal(dialogueLabel, DialogueLabel.SignalName.FinishedTyping);
|
||||
}
|
||||
|
||||
// Wait for input
|
||||
if (dialogueLine.Responses.Count > 0)
|
||||
{
|
||||
balloon.FocusMode = Control.FocusModeEnum.None;
|
||||
responsesMenu.Show();
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(dialogueLine.Time))
|
||||
{
|
||||
float time = 0f;
|
||||
if (!float.TryParse(dialogueLine.Time, out time))
|
||||
{
|
||||
time = dialogueLine.Text.Length * 0.02f;
|
||||
}
|
||||
await ToSignal(GetTree().CreateTimer(time), Timer.SignalName.Timeout);
|
||||
Next(dialogueLine.NextId);
|
||||
}
|
||||
else
|
||||
{
|
||||
isWaitingForInput = true;
|
||||
balloon.FocusMode = Control.FocusModeEnum.All;
|
||||
balloon.GrabFocus();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region signals
|
||||
|
||||
|
||||
private void OnMutated(Dictionary mutation)
|
||||
{
|
||||
if (!(bool)mutation["is_inline"])
|
||||
{
|
||||
isWaitingForInput = false;
|
||||
willHideBalloon = true;
|
||||
MutationCooldown.Start(0.1f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://5b3w40kwakl3
|
||||
@@ -0,0 +1,204 @@
|
||||
[gd_scene format=3 uid="uid://blvx2ftn6ycqd"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://5b3w40kwakl3" path="res://addons/dialogue_manager/example_balloon/ExampleBalloon.cs" id="1_36de5"]
|
||||
[ext_resource type="PackedScene" uid="uid://c3qmxhslp0dl7" path="res://addons/dialogue_manager/nodes/dialogue_label/DialogueLabel.tscn" id="2_a8ve6"]
|
||||
[ext_resource type="Script" uid="uid://bf0gf4upyxj5l" path="res://addons/dialogue_manager/nodes/responses_menu/DialogueResponsesMenu.cs" id="3_72ixx"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_spyqn"]
|
||||
content_margin_left = 20.0
|
||||
content_margin_top = 10.0
|
||||
content_margin_right = 20.0
|
||||
content_margin_bottom = 10.0
|
||||
bg_color = Color(0, 0, 0, 1)
|
||||
border_width_left = 3
|
||||
border_width_top = 3
|
||||
border_width_right = 3
|
||||
border_width_bottom = 3
|
||||
border_color = Color(0.329412, 0.329412, 0.329412, 1)
|
||||
corner_radius_top_left = 5
|
||||
corner_radius_top_right = 5
|
||||
corner_radius_bottom_right = 5
|
||||
corner_radius_bottom_left = 5
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ri4m3"]
|
||||
content_margin_left = 20.0
|
||||
content_margin_top = 10.0
|
||||
content_margin_right = 20.0
|
||||
content_margin_bottom = 10.0
|
||||
bg_color = Color(0.121569, 0.121569, 0.121569, 1)
|
||||
border_width_left = 3
|
||||
border_width_top = 3
|
||||
border_width_right = 3
|
||||
border_width_bottom = 3
|
||||
border_color = Color(1, 1, 1, 1)
|
||||
corner_radius_top_left = 5
|
||||
corner_radius_top_right = 5
|
||||
corner_radius_bottom_right = 5
|
||||
corner_radius_bottom_left = 5
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_e0njw"]
|
||||
content_margin_left = 20.0
|
||||
content_margin_top = 10.0
|
||||
content_margin_right = 20.0
|
||||
content_margin_bottom = 10.0
|
||||
bg_color = Color(0, 0, 0, 1)
|
||||
border_width_left = 3
|
||||
border_width_top = 3
|
||||
border_width_right = 3
|
||||
border_width_bottom = 3
|
||||
border_color = Color(0.6, 0.6, 0.6, 1)
|
||||
corner_radius_top_left = 5
|
||||
corner_radius_top_right = 5
|
||||
corner_radius_bottom_right = 5
|
||||
corner_radius_bottom_left = 5
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qkmqt"]
|
||||
bg_color = Color(0, 0, 0, 1)
|
||||
border_width_left = 3
|
||||
border_width_top = 3
|
||||
border_width_right = 3
|
||||
border_width_bottom = 3
|
||||
corner_radius_top_left = 5
|
||||
corner_radius_top_right = 5
|
||||
corner_radius_bottom_right = 5
|
||||
corner_radius_bottom_left = 5
|
||||
|
||||
[sub_resource type="Theme" id="Theme_qq3yp"]
|
||||
default_font_size = 20
|
||||
Button/styles/disabled = SubResource("StyleBoxFlat_spyqn")
|
||||
Button/styles/focus = SubResource("StyleBoxFlat_ri4m3")
|
||||
Button/styles/hover = SubResource("StyleBoxFlat_e0njw")
|
||||
Button/styles/normal = SubResource("StyleBoxFlat_e0njw")
|
||||
MarginContainer/constants/margin_bottom = 15
|
||||
MarginContainer/constants/margin_left = 30
|
||||
MarginContainer/constants/margin_right = 30
|
||||
MarginContainer/constants/margin_top = 15
|
||||
PanelContainer/styles/panel = SubResource("StyleBoxFlat_qkmqt")
|
||||
|
||||
[sub_resource type="Animation" id="Animation_nlsx6"]
|
||||
length = 0.001
|
||||
tracks/0/type = "bezier"
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
tracks/0/path = NodePath("Progress:position:y")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/keys = {
|
||||
"handle_modes": PackedInt32Array(0),
|
||||
"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0),
|
||||
"times": PackedFloat32Array(0)
|
||||
}
|
||||
|
||||
[sub_resource type="Animation" id="Animation_qkmqt"]
|
||||
resource_name = "progress"
|
||||
loop_mode = 1
|
||||
step = 0.1
|
||||
tracks/0/type = "bezier"
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
tracks/0/path = NodePath("Progress:position:y")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/keys = {
|
||||
"handle_modes": PackedInt32Array(0, 0, 0, 0, 0),
|
||||
"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0, 0, -0.25, 0, 0.2, -0.0157438, -4, -0.2, 0.0112069, 0.25, 0, -4, -0.25, 0, 0.25, 0, 0, -0.2, 0.00299701, 0.25, 0),
|
||||
"times": PackedFloat32Array(0, 0.1, 0.5, 0.6, 1)
|
||||
}
|
||||
|
||||
[sub_resource type="AnimationLibrary" id="AnimationLibrary_1337t"]
|
||||
_data = {
|
||||
&"RESET": SubResource("Animation_nlsx6"),
|
||||
&"progress": SubResource("Animation_qkmqt")
|
||||
}
|
||||
|
||||
[node name="ExampleBalloon" type="CanvasLayer" unique_id=615431052]
|
||||
layer = 100
|
||||
script = ExtResource("1_36de5")
|
||||
|
||||
[node name="Balloon" type="Control" parent="." unique_id=197696518]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme = SubResource("Theme_qq3yp")
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="Balloon" unique_id=1192375565]
|
||||
layout_mode = 1
|
||||
anchors_preset = 12
|
||||
anchor_top = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_top = -219.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 0
|
||||
|
||||
[node name="PanelContainer" type="PanelContainer" parent="Balloon/MarginContainer" unique_id=66397601]
|
||||
clip_children = 2
|
||||
layout_mode = 2
|
||||
mouse_filter = 1
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="Balloon/MarginContainer/PanelContainer" unique_id=338276858]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="Balloon/MarginContainer/PanelContainer/MarginContainer" unique_id=587923225]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer" unique_id=141270408]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="CharacterLabel" type="RichTextLabel" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1867683141]
|
||||
unique_name_in_owner = true
|
||||
modulate = Color(1, 1, 1, 0.501961)
|
||||
layout_mode = 2
|
||||
mouse_filter = 1
|
||||
bbcode_enabled = true
|
||||
text = "Character"
|
||||
fit_content = true
|
||||
scroll_active = false
|
||||
|
||||
[node name="DialogueLabel" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1739101537 instance=ExtResource("2_a8ve6")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
text = "Dialogue..."
|
||||
|
||||
[node name="Control" type="Control" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer" unique_id=534060310]
|
||||
custom_minimum_size = Vector2(20, 10)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 8
|
||||
|
||||
[node name="Progress" type="Polygon2D" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer/Control" unique_id=838836721]
|
||||
unique_name_in_owner = true
|
||||
polygon = PackedVector2Array(0, 0, 10, 10, 20, 0)
|
||||
|
||||
[node name="AnimationPlayer" type="AnimationPlayer" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer/Control" unique_id=473539640]
|
||||
libraries/ = SubResource("AnimationLibrary_1337t")
|
||||
autoplay = &"progress"
|
||||
|
||||
[node name="CenterContainer" type="CenterContainer" parent="Balloon" unique_id=473552711]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="ResponsesMenu" type="VBoxContainer" parent="Balloon/CenterContainer" unique_id=1321351341 node_paths=PackedStringArray("ResponseTemplate")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 8
|
||||
theme_override_constants/separation = 2
|
||||
alignment = 1
|
||||
script = ExtResource("3_72ixx")
|
||||
ResponseTemplate = NodePath("ResponseExample")
|
||||
|
||||
[node name="ResponseExample" type="Button" parent="Balloon/CenterContainer/ResponsesMenu" unique_id=1595872767]
|
||||
layout_mode = 2
|
||||
text = "Response example"
|
||||
|
||||
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="." unique_id=140912110]
|
||||
unique_name_in_owner = true
|
||||
@@ -0,0 +1,202 @@
|
||||
[gd_scene format=3 uid="uid://dwcmp07fyu5ah"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://5b3w40kwakl3" path="res://addons/dialogue_manager/example_balloon/ExampleBalloon.cs" id="1_s2gbs"]
|
||||
[ext_resource type="PackedScene" uid="uid://c3qmxhslp0dl7" path="res://addons/dialogue_manager/nodes/dialogue_label/DialogueLabel.tscn" id="2_hfvdi"]
|
||||
[ext_resource type="Script" uid="uid://bf0gf4upyxj5l" path="res://addons/dialogue_manager/nodes/responses_menu/DialogueResponsesMenu.cs" id="3_1j1j0"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_235ry"]
|
||||
content_margin_left = 6.0
|
||||
content_margin_top = 3.0
|
||||
content_margin_right = 6.0
|
||||
content_margin_bottom = 3.0
|
||||
bg_color = Color(0.0666667, 0.0666667, 0.0666667, 1)
|
||||
border_width_left = 1
|
||||
border_width_top = 1
|
||||
border_width_right = 1
|
||||
border_width_bottom = 1
|
||||
border_color = Color(0.345098, 0.345098, 0.345098, 1)
|
||||
corner_radius_top_left = 3
|
||||
corner_radius_top_right = 3
|
||||
corner_radius_bottom_right = 3
|
||||
corner_radius_bottom_left = 3
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ufjut"]
|
||||
content_margin_left = 6.0
|
||||
content_margin_top = 3.0
|
||||
content_margin_right = 6.0
|
||||
content_margin_bottom = 3.0
|
||||
bg_color = Color(0.227451, 0.227451, 0.227451, 1)
|
||||
border_width_left = 1
|
||||
border_width_top = 1
|
||||
border_width_right = 1
|
||||
border_width_bottom = 1
|
||||
border_color = Color(1, 1, 1, 1)
|
||||
corner_radius_top_left = 3
|
||||
corner_radius_top_right = 3
|
||||
corner_radius_bottom_right = 3
|
||||
corner_radius_bottom_left = 3
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_fcbqo"]
|
||||
content_margin_left = 6.0
|
||||
content_margin_top = 3.0
|
||||
content_margin_right = 6.0
|
||||
content_margin_bottom = 3.0
|
||||
bg_color = Color(0.0666667, 0.0666667, 0.0666667, 1)
|
||||
border_width_left = 1
|
||||
border_width_top = 1
|
||||
border_width_right = 1
|
||||
border_width_bottom = 1
|
||||
corner_radius_top_left = 3
|
||||
corner_radius_top_right = 3
|
||||
corner_radius_bottom_right = 3
|
||||
corner_radius_bottom_left = 3
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_t6i7a"]
|
||||
content_margin_left = 6.0
|
||||
content_margin_top = 3.0
|
||||
content_margin_right = 6.0
|
||||
content_margin_bottom = 3.0
|
||||
bg_color = Color(0.0666667, 0.0666667, 0.0666667, 1)
|
||||
border_width_left = 1
|
||||
border_width_top = 1
|
||||
border_width_right = 1
|
||||
border_width_bottom = 1
|
||||
corner_radius_top_left = 3
|
||||
corner_radius_top_right = 3
|
||||
corner_radius_bottom_right = 3
|
||||
corner_radius_bottom_left = 3
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_i6nbm"]
|
||||
bg_color = Color(0, 0, 0, 1)
|
||||
border_width_left = 1
|
||||
border_width_top = 1
|
||||
border_width_right = 1
|
||||
border_width_bottom = 1
|
||||
corner_radius_top_left = 3
|
||||
corner_radius_top_right = 3
|
||||
corner_radius_bottom_right = 3
|
||||
corner_radius_bottom_left = 3
|
||||
|
||||
[sub_resource type="Theme" id="Theme_qq3yp"]
|
||||
default_font_size = 8
|
||||
Button/styles/disabled = SubResource("StyleBoxFlat_235ry")
|
||||
Button/styles/focus = SubResource("StyleBoxFlat_ufjut")
|
||||
Button/styles/hover = SubResource("StyleBoxFlat_fcbqo")
|
||||
Button/styles/normal = SubResource("StyleBoxFlat_t6i7a")
|
||||
MarginContainer/constants/margin_bottom = 4
|
||||
MarginContainer/constants/margin_left = 8
|
||||
MarginContainer/constants/margin_right = 8
|
||||
MarginContainer/constants/margin_top = 4
|
||||
PanelContainer/styles/panel = SubResource("StyleBoxFlat_i6nbm")
|
||||
|
||||
[sub_resource type="Animation" id="Animation_i6nbm"]
|
||||
resource_name = "progress"
|
||||
loop_mode = 1
|
||||
step = 0.1
|
||||
tracks/0/type = "bezier"
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
tracks/0/path = NodePath("Progress:position:y")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/keys = {
|
||||
"handle_modes": PackedInt32Array(0, 0, 0, 0, 0),
|
||||
"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0, 0, -0.25, 0, 0.2, -0.0157438, -1, -0.2, 0.0112069, 0.25, 0, -1, -0.25, 0, 0.25, 0, 0, -0.2, 0.00299701, 0.25, 0),
|
||||
"times": PackedFloat32Array(0, 0.1, 0.5, 0.6, 1)
|
||||
}
|
||||
|
||||
[sub_resource type="AnimationLibrary" id="AnimationLibrary_6b6c8"]
|
||||
_data = {
|
||||
&"progress": SubResource("Animation_i6nbm")
|
||||
}
|
||||
|
||||
[node name="ExampleBalloon" type="CanvasLayer" unique_id=1448364161]
|
||||
layer = 100
|
||||
script = ExtResource("1_s2gbs")
|
||||
|
||||
[node name="Balloon" type="Control" parent="." unique_id=1854621134]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme = SubResource("Theme_qq3yp")
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="Balloon" unique_id=682848477]
|
||||
layout_mode = 1
|
||||
anchors_preset = 12
|
||||
anchor_top = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_top = -71.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 0
|
||||
|
||||
[node name="PanelContainer" type="PanelContainer" parent="Balloon/MarginContainer" unique_id=1589990687]
|
||||
clip_children = 2
|
||||
layout_mode = 2
|
||||
mouse_filter = 1
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="Balloon/MarginContainer/PanelContainer" unique_id=1001222780]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="Balloon/MarginContainer/PanelContainer/MarginContainer" unique_id=953735783]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer" unique_id=636314399]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="CharacterLabel" type="RichTextLabel" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer/VBoxContainer" unique_id=571791881]
|
||||
unique_name_in_owner = true
|
||||
modulate = Color(1, 1, 1, 0.501961)
|
||||
layout_mode = 2
|
||||
mouse_filter = 1
|
||||
bbcode_enabled = true
|
||||
text = "Character"
|
||||
fit_content = true
|
||||
scroll_active = false
|
||||
|
||||
[node name="DialogueLabel" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer/VBoxContainer" unique_id=388630144 instance=ExtResource("2_hfvdi")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
text = "Dialogue..."
|
||||
|
||||
[node name="Control" type="Control" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer" unique_id=2030531972]
|
||||
custom_minimum_size = Vector2(5, 3)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 8
|
||||
|
||||
[node name="Progress" type="Polygon2D" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer/Control" unique_id=1185159698]
|
||||
unique_name_in_owner = true
|
||||
position = Vector2(0, -0.455998)
|
||||
polygon = PackedVector2Array(0, 0, 2.5, 3, 5, 0)
|
||||
|
||||
[node name="AnimationPlayer" type="AnimationPlayer" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer/Control" unique_id=1445298592]
|
||||
libraries/ = SubResource("AnimationLibrary_6b6c8")
|
||||
autoplay = &"progress"
|
||||
|
||||
[node name="CenterContainer" type="CenterContainer" parent="Balloon" unique_id=1054790561]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="ResponsesMenu" type="VBoxContainer" parent="Balloon/CenterContainer" unique_id=260756352]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 8
|
||||
theme_override_constants/separation = 2
|
||||
script = ExtResource("3_1j1j0")
|
||||
|
||||
[node name="ResponseExample" type="Button" parent="Balloon/CenterContainer/ResponsesMenu" unique_id=1031268561]
|
||||
layout_mode = 2
|
||||
text = "Response Example"
|
||||
|
||||
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="." unique_id=1157057297]
|
||||
unique_name_in_owner = true
|
||||
@@ -0,0 +1,226 @@
|
||||
class_name DialogueManagerExampleBalloon extends CanvasLayer
|
||||
## A basic dialogue balloon for use with Dialogue Manager.
|
||||
|
||||
|
||||
## The dialogue resource
|
||||
@export var dialogue_resource: DialogueResource
|
||||
|
||||
## Start from a given cue when using balloon as a [Node] in a scene.
|
||||
@export var start_from_cue: String = ""
|
||||
|
||||
## If running as a [Node] in a scene then auto start the dialogue.
|
||||
@export var auto_start: bool = false
|
||||
|
||||
## If all other input is blocked as long as dialogue is shown.
|
||||
@export var will_block_other_input: bool = true
|
||||
|
||||
## The action to use for advancing the dialogue
|
||||
@export var next_action: StringName = &"ui_accept"
|
||||
|
||||
## The action to use to skip typing the dialogue
|
||||
@export var skip_action: StringName = &"ui_cancel"
|
||||
|
||||
## A sound player for voice lines (if they exist).
|
||||
@onready var audio_stream_player: AudioStreamPlayer = %AudioStreamPlayer
|
||||
|
||||
## Temporary game states
|
||||
var temporary_game_states: Array = []
|
||||
|
||||
## See if we are waiting for the player
|
||||
var is_waiting_for_input: bool = false
|
||||
|
||||
## See if we are running a long mutation and should hide the balloon
|
||||
var will_hide_balloon: bool = false
|
||||
|
||||
## A dictionary to store any ephemeral variables
|
||||
var locals: Dictionary = {}
|
||||
|
||||
var _locale: String = TranslationServer.get_locale()
|
||||
|
||||
## The current line
|
||||
var dialogue_line: DialogueLine:
|
||||
set(value):
|
||||
if value:
|
||||
dialogue_line = value
|
||||
apply_dialogue_line()
|
||||
else:
|
||||
# The dialogue has finished so close the balloon
|
||||
if owner == null:
|
||||
queue_free()
|
||||
else:
|
||||
hide()
|
||||
get:
|
||||
return dialogue_line
|
||||
|
||||
## A cooldown timer for delaying the balloon hide when encountering a mutation.
|
||||
var mutation_cooldown: Timer = Timer.new()
|
||||
|
||||
## The base balloon anchor
|
||||
@onready var balloon: Control = %Balloon
|
||||
|
||||
## The label showing the name of the currently speaking character
|
||||
@onready var character_label: RichTextLabel = %CharacterLabel
|
||||
|
||||
## The label showing the currently spoken dialogue
|
||||
@onready var dialogue_label: DialogueLabel = %DialogueLabel
|
||||
|
||||
## The menu of responses
|
||||
@onready var responses_menu: DialogueResponsesMenu = %ResponsesMenu
|
||||
|
||||
## Indicator to show that player can progress dialogue.
|
||||
@onready var progress: Polygon2D = %Progress
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
balloon.hide()
|
||||
Engine.get_singleton("DialogueManager").mutated.connect(_on_mutated)
|
||||
|
||||
# If the responses menu doesn't have a next action set, use this one
|
||||
if responses_menu.next_action.is_empty():
|
||||
responses_menu.next_action = next_action
|
||||
|
||||
mutation_cooldown.timeout.connect(_on_mutation_cooldown_timeout)
|
||||
add_child(mutation_cooldown)
|
||||
|
||||
if auto_start:
|
||||
if not is_instance_valid(dialogue_resource):
|
||||
assert(false, DMConstants.get_error_message(DMConstants.ERR_MISSING_RESOURCE_FOR_AUTOSTART))
|
||||
start()
|
||||
|
||||
# EXAMPLE MESSAGE
|
||||
var warning: Button = Button.new()
|
||||
warning.text = DMConstants.translate("This is an example balloon. Create your own balloon in 'Project > Tools > Dialogue > Create Balloon...'")
|
||||
warning.disabled = true
|
||||
warning.set_anchors_and_offsets_preset(Control.PRESET_TOP_WIDE)
|
||||
balloon.add_child(warning)
|
||||
balloon.move_child(warning, 0)
|
||||
# /EXAMPLE MESSAGE
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if is_instance_valid(dialogue_line):
|
||||
progress.visible = not dialogue_label.is_typing and dialogue_line.responses.size() == 0 and not dialogue_line.has_tag("voice")
|
||||
|
||||
|
||||
func _unhandled_input(_event: InputEvent) -> void:
|
||||
# Only the balloon is allowed to handle input while it's showing
|
||||
if will_block_other_input:
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
## Detect a change of locale and update the current dialogue line to show the new language
|
||||
if what == NOTIFICATION_TRANSLATION_CHANGED and _locale != TranslationServer.get_locale() and is_instance_valid(dialogue_label):
|
||||
_locale = TranslationServer.get_locale()
|
||||
var visible_ratio: float = dialogue_label.visible_ratio
|
||||
await dialogue_line.refresh()
|
||||
if visible_ratio < 1:
|
||||
dialogue_label.skip_typing()
|
||||
|
||||
|
||||
## Start some dialogue
|
||||
func start(with_dialogue_resource: DialogueResource = null, cue: String = "", extra_game_states: Array = []) -> void:
|
||||
temporary_game_states = [self] + extra_game_states
|
||||
is_waiting_for_input = false
|
||||
if is_instance_valid(with_dialogue_resource):
|
||||
dialogue_resource = with_dialogue_resource
|
||||
if not cue.is_empty():
|
||||
start_from_cue = cue
|
||||
dialogue_line = await dialogue_resource.get_next_dialogue_line(start_from_cue, temporary_game_states)
|
||||
show()
|
||||
|
||||
|
||||
## Apply any changes to the balloon given a new [DialogueLine].
|
||||
func apply_dialogue_line() -> void:
|
||||
mutation_cooldown.stop()
|
||||
|
||||
progress.hide()
|
||||
is_waiting_for_input = false
|
||||
balloon.focus_mode = Control.FOCUS_ALL
|
||||
balloon.grab_focus()
|
||||
|
||||
character_label.visible = not dialogue_line.character.is_empty()
|
||||
character_label.text = tr(dialogue_line.character, "dialogue")
|
||||
|
||||
dialogue_label.hide()
|
||||
dialogue_label.dialogue_line = dialogue_line
|
||||
|
||||
responses_menu.hide()
|
||||
responses_menu.responses = dialogue_line.responses
|
||||
|
||||
# Show our balloon
|
||||
balloon.show()
|
||||
will_hide_balloon = false
|
||||
|
||||
dialogue_label.show()
|
||||
if not dialogue_line.text.is_empty():
|
||||
dialogue_label.type_out()
|
||||
await dialogue_label.finished_typing
|
||||
|
||||
# Wait for next line
|
||||
if dialogue_line.has_tag("voice"):
|
||||
audio_stream_player.stream = load(dialogue_line.get_tag_value("voice"))
|
||||
audio_stream_player.play()
|
||||
await audio_stream_player.finished
|
||||
next(dialogue_line.next_id)
|
||||
elif dialogue_line.responses.size() > 0:
|
||||
balloon.focus_mode = Control.FOCUS_NONE
|
||||
responses_menu.show()
|
||||
elif dialogue_line.time != "":
|
||||
var time: float = dialogue_line.text.length() * 0.02 if dialogue_line.time == "auto" else dialogue_line.time.to_float()
|
||||
await get_tree().create_timer(time).timeout
|
||||
next(dialogue_line.next_id)
|
||||
else:
|
||||
is_waiting_for_input = true
|
||||
balloon.focus_mode = Control.FOCUS_ALL
|
||||
balloon.grab_focus()
|
||||
|
||||
|
||||
## Go to the next line
|
||||
func next(next_id: String) -> void:
|
||||
dialogue_line = await dialogue_resource.get_next_dialogue_line(next_id, temporary_game_states)
|
||||
|
||||
|
||||
#region Signals
|
||||
|
||||
|
||||
func _on_mutation_cooldown_timeout() -> void:
|
||||
if will_hide_balloon:
|
||||
will_hide_balloon = false
|
||||
balloon.hide()
|
||||
|
||||
|
||||
func _on_mutated(mutation: Dictionary) -> void:
|
||||
if not mutation.is_inline:
|
||||
is_waiting_for_input = false
|
||||
will_hide_balloon = true
|
||||
mutation_cooldown.start(0.1)
|
||||
|
||||
|
||||
func _on_balloon_gui_input(event: InputEvent) -> void:
|
||||
# See if we need to skip typing of the dialogue
|
||||
if dialogue_label.is_typing:
|
||||
var mouse_was_clicked: bool = event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed()
|
||||
var skip_button_was_pressed: bool = event.is_action_pressed(skip_action)
|
||||
if mouse_was_clicked or skip_button_was_pressed:
|
||||
get_viewport().set_input_as_handled()
|
||||
dialogue_label.skip_typing()
|
||||
return
|
||||
|
||||
if not is_waiting_for_input: return
|
||||
if dialogue_line.responses.size() > 0: return
|
||||
|
||||
# When there are no response options the balloon itself is the clickable thing
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
if event is InputEventMouseButton and event.is_pressed() and event.button_index == MOUSE_BUTTON_LEFT:
|
||||
next(dialogue_line.next_id)
|
||||
elif event.is_action_pressed(next_action) and get_viewport().gui_get_focus_owner() == balloon:
|
||||
next(dialogue_line.next_id)
|
||||
|
||||
|
||||
func _on_responses_menu_response_selected(response: DialogueResponse) -> void:
|
||||
next(response.next_id)
|
||||
|
||||
|
||||
#endregion
|
||||
@@ -0,0 +1 @@
|
||||
uid://d1wt4ma6055l8
|
||||
@@ -0,0 +1,207 @@
|
||||
[gd_scene format=3 uid="uid://73jm5qjy52vq"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://d1wt4ma6055l8" path="res://addons/dialogue_manager/example_balloon/example_balloon.gd" id="1_36de5"]
|
||||
[ext_resource type="PackedScene" uid="uid://ckvgyvclnwggo" path="res://addons/dialogue_manager/nodes/dialogue_label/dialogue_label.tscn" id="2_a8ve6"]
|
||||
[ext_resource type="Script" uid="uid://bb52rsfwhkxbn" path="res://addons/dialogue_manager/nodes/responses_menu/dialogue_responses_menu.gd" id="3_72ixx"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_spyqn"]
|
||||
content_margin_left = 20.0
|
||||
content_margin_top = 10.0
|
||||
content_margin_right = 20.0
|
||||
content_margin_bottom = 10.0
|
||||
bg_color = Color(0, 0, 0, 1)
|
||||
border_width_left = 3
|
||||
border_width_top = 3
|
||||
border_width_right = 3
|
||||
border_width_bottom = 3
|
||||
border_color = Color(0.329412, 0.329412, 0.329412, 1)
|
||||
corner_radius_top_left = 5
|
||||
corner_radius_top_right = 5
|
||||
corner_radius_bottom_right = 5
|
||||
corner_radius_bottom_left = 5
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ri4m3"]
|
||||
content_margin_left = 20.0
|
||||
content_margin_top = 10.0
|
||||
content_margin_right = 20.0
|
||||
content_margin_bottom = 10.0
|
||||
bg_color = Color(0.121569, 0.121569, 0.121569, 1)
|
||||
border_width_left = 3
|
||||
border_width_top = 3
|
||||
border_width_right = 3
|
||||
border_width_bottom = 3
|
||||
border_color = Color(1, 1, 1, 1)
|
||||
corner_radius_top_left = 5
|
||||
corner_radius_top_right = 5
|
||||
corner_radius_bottom_right = 5
|
||||
corner_radius_bottom_left = 5
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_e0njw"]
|
||||
content_margin_left = 20.0
|
||||
content_margin_top = 10.0
|
||||
content_margin_right = 20.0
|
||||
content_margin_bottom = 10.0
|
||||
bg_color = Color(0, 0, 0, 1)
|
||||
border_width_left = 3
|
||||
border_width_top = 3
|
||||
border_width_right = 3
|
||||
border_width_bottom = 3
|
||||
border_color = Color(0.6, 0.6, 0.6, 1)
|
||||
corner_radius_top_left = 5
|
||||
corner_radius_top_right = 5
|
||||
corner_radius_bottom_right = 5
|
||||
corner_radius_bottom_left = 5
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_qkmqt"]
|
||||
bg_color = Color(0, 0, 0, 1)
|
||||
border_width_left = 3
|
||||
border_width_top = 3
|
||||
border_width_right = 3
|
||||
border_width_bottom = 3
|
||||
corner_radius_top_left = 5
|
||||
corner_radius_top_right = 5
|
||||
corner_radius_bottom_right = 5
|
||||
corner_radius_bottom_left = 5
|
||||
|
||||
[sub_resource type="Theme" id="Theme_qq3yp"]
|
||||
default_font_size = 20
|
||||
Button/styles/disabled = SubResource("StyleBoxFlat_spyqn")
|
||||
Button/styles/focus = SubResource("StyleBoxFlat_ri4m3")
|
||||
Button/styles/hover = SubResource("StyleBoxFlat_e0njw")
|
||||
Button/styles/normal = SubResource("StyleBoxFlat_e0njw")
|
||||
MarginContainer/constants/margin_bottom = 15
|
||||
MarginContainer/constants/margin_left = 30
|
||||
MarginContainer/constants/margin_right = 30
|
||||
MarginContainer/constants/margin_top = 15
|
||||
PanelContainer/styles/panel = SubResource("StyleBoxFlat_qkmqt")
|
||||
|
||||
[sub_resource type="Animation" id="Animation_nlsx6"]
|
||||
length = 0.001
|
||||
tracks/0/type = "bezier"
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
tracks/0/path = NodePath("Progress:position:y")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/keys = {
|
||||
"handle_modes": PackedInt32Array(0),
|
||||
"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0),
|
||||
"times": PackedFloat32Array(0)
|
||||
}
|
||||
|
||||
[sub_resource type="Animation" id="Animation_qkmqt"]
|
||||
resource_name = "progress"
|
||||
loop_mode = 1
|
||||
step = 0.1
|
||||
tracks/0/type = "bezier"
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
tracks/0/path = NodePath("Progress:position:y")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/keys = {
|
||||
"handle_modes": PackedInt32Array(0, 0, 0, 0, 0),
|
||||
"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0, 0, -0.25, 0, 0.2, -0.0157438, -4, -0.2, 0.0112069, 0.25, 0, -4, -0.25, 0, 0.25, 0, 0, -0.2, 0.00299701, 0.25, 0),
|
||||
"times": PackedFloat32Array(0, 0.1, 0.5, 0.6, 1)
|
||||
}
|
||||
|
||||
[sub_resource type="AnimationLibrary" id="AnimationLibrary_1337t"]
|
||||
_data = {
|
||||
&"RESET": SubResource("Animation_nlsx6"),
|
||||
&"progress": SubResource("Animation_qkmqt")
|
||||
}
|
||||
|
||||
[node name="ExampleBalloon" type="CanvasLayer" unique_id=38036521]
|
||||
layer = 100
|
||||
script = ExtResource("1_36de5")
|
||||
|
||||
[node name="Balloon" type="Control" parent="." unique_id=154399927]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme = SubResource("Theme_qq3yp")
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="Balloon" unique_id=1302081376]
|
||||
layout_mode = 1
|
||||
anchors_preset = 12
|
||||
anchor_top = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_top = -219.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 0
|
||||
|
||||
[node name="PanelContainer" type="PanelContainer" parent="Balloon/MarginContainer" unique_id=2145270309]
|
||||
clip_children = 2
|
||||
layout_mode = 2
|
||||
mouse_filter = 1
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="Balloon/MarginContainer/PanelContainer" unique_id=903640783]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="Balloon/MarginContainer/PanelContainer/MarginContainer" unique_id=1685890612]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer" unique_id=1890134056]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="CharacterLabel" type="RichTextLabel" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1567782692]
|
||||
unique_name_in_owner = true
|
||||
modulate = Color(1, 1, 1, 0.501961)
|
||||
layout_mode = 2
|
||||
mouse_filter = 1
|
||||
bbcode_enabled = true
|
||||
text = "Character"
|
||||
fit_content = true
|
||||
scroll_active = false
|
||||
|
||||
[node name="DialogueLabel" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1301798052 instance=ExtResource("2_a8ve6")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
text = "Dialogue..."
|
||||
|
||||
[node name="Control" type="Control" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer" unique_id=418507161]
|
||||
custom_minimum_size = Vector2(20, 10)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 8
|
||||
|
||||
[node name="Progress" type="Polygon2D" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer/Control" unique_id=2091292889]
|
||||
unique_name_in_owner = true
|
||||
polygon = PackedVector2Array(0, 0, 10, 10, 20, 0)
|
||||
|
||||
[node name="AnimationPlayer" type="AnimationPlayer" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer/Control" unique_id=1602634471]
|
||||
libraries/ = SubResource("AnimationLibrary_1337t")
|
||||
autoplay = &"progress"
|
||||
|
||||
[node name="CenterContainer" type="CenterContainer" parent="Balloon" unique_id=1087901387]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="ResponsesMenu" type="VBoxContainer" parent="Balloon/CenterContainer" unique_id=1801308483 node_paths=PackedStringArray("response_template")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 8
|
||||
theme_override_constants/separation = 2
|
||||
alignment = 1
|
||||
script = ExtResource("3_72ixx")
|
||||
response_template = NodePath("ResponseExample")
|
||||
|
||||
[node name="ResponseExample" type="Button" parent="Balloon/CenterContainer/ResponsesMenu" unique_id=1446810977]
|
||||
layout_mode = 2
|
||||
text = "Response example"
|
||||
|
||||
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="." unique_id=1162558754]
|
||||
unique_name_in_owner = true
|
||||
|
||||
[connection signal="gui_input" from="Balloon" to="." method="_on_balloon_gui_input"]
|
||||
[connection signal="response_selected" from="Balloon/CenterContainer/ResponsesMenu" to="." method="_on_responses_menu_response_selected"]
|
||||
@@ -0,0 +1,204 @@
|
||||
[gd_scene format=3 uid="uid://13s5spsk34qu"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://d1wt4ma6055l8" path="res://addons/dialogue_manager/example_balloon/example_balloon.gd" id="1_s2gbs"]
|
||||
[ext_resource type="PackedScene" uid="uid://ckvgyvclnwggo" path="res://addons/dialogue_manager/nodes/dialogue_label/dialogue_label.tscn" id="2_hfvdi"]
|
||||
[ext_resource type="Script" uid="uid://bb52rsfwhkxbn" path="res://addons/dialogue_manager/nodes/responses_menu/dialogue_responses_menu.gd" id="3_1j1j0"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_235ry"]
|
||||
content_margin_left = 6.0
|
||||
content_margin_top = 3.0
|
||||
content_margin_right = 6.0
|
||||
content_margin_bottom = 3.0
|
||||
bg_color = Color(0.0666667, 0.0666667, 0.0666667, 1)
|
||||
border_width_left = 1
|
||||
border_width_top = 1
|
||||
border_width_right = 1
|
||||
border_width_bottom = 1
|
||||
border_color = Color(0.345098, 0.345098, 0.345098, 1)
|
||||
corner_radius_top_left = 3
|
||||
corner_radius_top_right = 3
|
||||
corner_radius_bottom_right = 3
|
||||
corner_radius_bottom_left = 3
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ufjut"]
|
||||
content_margin_left = 6.0
|
||||
content_margin_top = 3.0
|
||||
content_margin_right = 6.0
|
||||
content_margin_bottom = 3.0
|
||||
bg_color = Color(0.227451, 0.227451, 0.227451, 1)
|
||||
border_width_left = 1
|
||||
border_width_top = 1
|
||||
border_width_right = 1
|
||||
border_width_bottom = 1
|
||||
border_color = Color(1, 1, 1, 1)
|
||||
corner_radius_top_left = 3
|
||||
corner_radius_top_right = 3
|
||||
corner_radius_bottom_right = 3
|
||||
corner_radius_bottom_left = 3
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_fcbqo"]
|
||||
content_margin_left = 6.0
|
||||
content_margin_top = 3.0
|
||||
content_margin_right = 6.0
|
||||
content_margin_bottom = 3.0
|
||||
bg_color = Color(0.0666667, 0.0666667, 0.0666667, 1)
|
||||
border_width_left = 1
|
||||
border_width_top = 1
|
||||
border_width_right = 1
|
||||
border_width_bottom = 1
|
||||
corner_radius_top_left = 3
|
||||
corner_radius_top_right = 3
|
||||
corner_radius_bottom_right = 3
|
||||
corner_radius_bottom_left = 3
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_t6i7a"]
|
||||
content_margin_left = 6.0
|
||||
content_margin_top = 3.0
|
||||
content_margin_right = 6.0
|
||||
content_margin_bottom = 3.0
|
||||
bg_color = Color(0.0666667, 0.0666667, 0.0666667, 1)
|
||||
border_width_left = 1
|
||||
border_width_top = 1
|
||||
border_width_right = 1
|
||||
border_width_bottom = 1
|
||||
corner_radius_top_left = 3
|
||||
corner_radius_top_right = 3
|
||||
corner_radius_bottom_right = 3
|
||||
corner_radius_bottom_left = 3
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_i6nbm"]
|
||||
bg_color = Color(0, 0, 0, 1)
|
||||
border_width_left = 1
|
||||
border_width_top = 1
|
||||
border_width_right = 1
|
||||
border_width_bottom = 1
|
||||
corner_radius_top_left = 3
|
||||
corner_radius_top_right = 3
|
||||
corner_radius_bottom_right = 3
|
||||
corner_radius_bottom_left = 3
|
||||
|
||||
[sub_resource type="Theme" id="Theme_qq3yp"]
|
||||
default_font_size = 8
|
||||
Button/styles/disabled = SubResource("StyleBoxFlat_235ry")
|
||||
Button/styles/focus = SubResource("StyleBoxFlat_ufjut")
|
||||
Button/styles/hover = SubResource("StyleBoxFlat_fcbqo")
|
||||
Button/styles/normal = SubResource("StyleBoxFlat_t6i7a")
|
||||
MarginContainer/constants/margin_bottom = 4
|
||||
MarginContainer/constants/margin_left = 8
|
||||
MarginContainer/constants/margin_right = 8
|
||||
MarginContainer/constants/margin_top = 4
|
||||
PanelContainer/styles/panel = SubResource("StyleBoxFlat_i6nbm")
|
||||
|
||||
[sub_resource type="Animation" id="Animation_i6nbm"]
|
||||
resource_name = "progress"
|
||||
loop_mode = 1
|
||||
step = 0.1
|
||||
tracks/0/type = "bezier"
|
||||
tracks/0/imported = false
|
||||
tracks/0/enabled = true
|
||||
tracks/0/path = NodePath("Progress:position:y")
|
||||
tracks/0/interp = 1
|
||||
tracks/0/loop_wrap = true
|
||||
tracks/0/keys = {
|
||||
"handle_modes": PackedInt32Array(0, 0, 0, 0, 0),
|
||||
"points": PackedFloat32Array(0, -0.25, 0, 0.25, 0, 0, -0.25, 0, 0.2, -0.0157438, -1, -0.2, 0.0112069, 0.25, 0, -1, -0.25, 0, 0.25, 0, 0, -0.2, 0.00299701, 0.25, 0),
|
||||
"times": PackedFloat32Array(0, 0.1, 0.5, 0.6, 1)
|
||||
}
|
||||
|
||||
[sub_resource type="AnimationLibrary" id="AnimationLibrary_6b6c8"]
|
||||
_data = {
|
||||
&"progress": SubResource("Animation_i6nbm")
|
||||
}
|
||||
|
||||
[node name="ExampleBalloon" type="CanvasLayer" unique_id=1912754290]
|
||||
layer = 100
|
||||
script = ExtResource("1_s2gbs")
|
||||
|
||||
[node name="Balloon" type="Control" parent="." unique_id=1177332107]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme = SubResource("Theme_qq3yp")
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="Balloon" unique_id=243547760]
|
||||
layout_mode = 1
|
||||
anchors_preset = 12
|
||||
anchor_top = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_top = -71.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 0
|
||||
|
||||
[node name="PanelContainer" type="PanelContainer" parent="Balloon/MarginContainer" unique_id=649738838]
|
||||
clip_children = 2
|
||||
layout_mode = 2
|
||||
mouse_filter = 1
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="Balloon/MarginContainer/PanelContainer" unique_id=1503769570]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="Balloon/MarginContainer/PanelContainer/MarginContainer" unique_id=476747906]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer" unique_id=2111896767]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="CharacterLabel" type="RichTextLabel" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1215135358]
|
||||
unique_name_in_owner = true
|
||||
modulate = Color(1, 1, 1, 0.501961)
|
||||
layout_mode = 2
|
||||
mouse_filter = 1
|
||||
bbcode_enabled = true
|
||||
text = "Character"
|
||||
fit_content = true
|
||||
scroll_active = false
|
||||
|
||||
[node name="DialogueLabel" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer/VBoxContainer" unique_id=1677336664 instance=ExtResource("2_hfvdi")]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
text = "Dialogue..."
|
||||
|
||||
[node name="Control" type="Control" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer" unique_id=351729490]
|
||||
custom_minimum_size = Vector2(5, 3)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 8
|
||||
|
||||
[node name="Progress" type="Polygon2D" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer/Control" unique_id=570583621]
|
||||
unique_name_in_owner = true
|
||||
polygon = PackedVector2Array(0, 0, 2.5, 3, 5, 0)
|
||||
|
||||
[node name="AnimationPlayer" type="AnimationPlayer" parent="Balloon/MarginContainer/PanelContainer/MarginContainer/HBoxContainer/Control" unique_id=1392989128]
|
||||
libraries/ = SubResource("AnimationLibrary_6b6c8")
|
||||
autoplay = &"progress"
|
||||
|
||||
[node name="CenterContainer" type="CenterContainer" parent="Balloon" unique_id=1047265792]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="ResponsesMenu" type="VBoxContainer" parent="Balloon/CenterContainer" unique_id=930429145]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 8
|
||||
theme_override_constants/separation = 2
|
||||
script = ExtResource("3_1j1j0")
|
||||
|
||||
[node name="ResponseExample" type="Button" parent="Balloon/CenterContainer/ResponsesMenu" unique_id=1555320070]
|
||||
layout_mode = 2
|
||||
text = "Response Example"
|
||||
|
||||
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="." unique_id=963979321]
|
||||
unique_name_in_owner = true
|
||||
|
||||
[connection signal="gui_input" from="Balloon" to="." method="_on_balloon_gui_input"]
|
||||
[connection signal="response_selected" from="Balloon/CenterContainer/ResponsesMenu" to="." method="_on_responses_menu_response_selected"]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user