Add initial implementation of Dialogue Manager; include core scripts, scenes, and resources
This commit is contained in:
1111
addons/dialogue_manager/compiler/compilation.gd
vendored
Normal file
1111
addons/dialogue_manager/compiler/compilation.gd
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
addons/dialogue_manager/compiler/compilation.gd.uid
Normal file
1
addons/dialogue_manager/compiler/compilation.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dsgpnyqg6cprg
|
161
addons/dialogue_manager/compiler/compiled_line.gd
vendored
Normal file
161
addons/dialogue_manager/compiler/compiled_line.gd
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
## A compiled line of dialogue.
|
||||
class_name DMCompiledLine extends RefCounted
|
||||
|
||||
|
||||
## The ID of the line
|
||||
var id: String
|
||||
## The translation key (or static line ID).
|
||||
var translation_key: String = ""
|
||||
## The type of line.
|
||||
var type: String = ""
|
||||
## The character name.
|
||||
var character: String = ""
|
||||
## Any interpolation expressions for the character name.
|
||||
var character_replacements: Array[Dictionary] = []
|
||||
## The text of the line.
|
||||
var text: String = ""
|
||||
## Any interpolation expressions for the text.
|
||||
var text_replacements: Array[Dictionary] = []
|
||||
## Any response siblings associated with this line.
|
||||
var responses: PackedStringArray = []
|
||||
## Any randomise or case siblings for this line.
|
||||
var siblings: Array[Dictionary] = []
|
||||
## Any lines said simultaneously.
|
||||
var concurrent_lines: PackedStringArray = []
|
||||
## Any tags on this line.
|
||||
var tags: PackedStringArray = []
|
||||
## The condition or mutation expression for this line.
|
||||
var expression: Dictionary = {}
|
||||
## The 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 null,
|
||||
text if text != "" else null,
|
||||
expression if expression.size() > 0 else null,
|
||||
"[%s]" % [",".join(tags)] if tags.size() > 0 else null,
|
||||
str(siblings) if siblings.size() > 0 else null,
|
||||
str(responses) if responses.size() > 0 else null,
|
||||
"=> END" if "end" in next_id else "=> %s" % [next_id],
|
||||
"(~> %s)" % [next_sibling_id] if next_sibling_id != "" else null,
|
||||
"(==> %s)" % [next_id_after] if next_id_after != "" else null,
|
||||
].filter(func(item): return item != null)
|
||||
|
||||
return " ".join(s)
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
|
||||
## Express this line as a [Dictionary] that can be stored in a resource.
|
||||
func to_data() -> Dictionary:
|
||||
var d: Dictionary = {
|
||||
id = id,
|
||||
type = type,
|
||||
next_id = next_id
|
||||
}
|
||||
|
||||
if next_id_expression.size() > 0:
|
||||
d.next_id_expression = next_id_expression
|
||||
|
||||
match type:
|
||||
DMConstants.TYPE_CONDITION:
|
||||
d.condition = expression
|
||||
if not next_sibling_id.is_empty():
|
||||
d.next_sibling_id = next_sibling_id
|
||||
d.next_id_after = next_id_after
|
||||
|
||||
DMConstants.TYPE_WHILE:
|
||||
d.condition = expression
|
||||
d.next_id_after = next_id_after
|
||||
|
||||
DMConstants.TYPE_MATCH:
|
||||
d.condition = expression
|
||||
d.next_id_after = next_id_after
|
||||
d.cases = siblings
|
||||
|
||||
DMConstants.TYPE_MUTATION:
|
||||
d.mutation = expression
|
||||
|
||||
DMConstants.TYPE_GOTO:
|
||||
d.is_snippet = is_snippet
|
||||
d.next_id_after = next_id_after
|
||||
if not siblings.is_empty():
|
||||
d.siblings = siblings
|
||||
|
||||
DMConstants.TYPE_RANDOM:
|
||||
d.siblings = siblings
|
||||
|
||||
DMConstants.TYPE_RESPONSE:
|
||||
d.text = text
|
||||
|
||||
if not responses.is_empty():
|
||||
d.responses = responses
|
||||
|
||||
if translation_key != text:
|
||||
d.translation_key = translation_key
|
||||
if not expression.is_empty():
|
||||
d.condition = expression
|
||||
if not character.is_empty():
|
||||
d.character = character
|
||||
if not character_replacements.is_empty():
|
||||
d.character_replacements = character_replacements
|
||||
if not text_replacements.is_empty():
|
||||
d.text_replacements = text_replacements
|
||||
if not tags.is_empty():
|
||||
d.tags = tags
|
||||
if not notes.is_empty():
|
||||
d.notes = notes
|
||||
if not expression_text.is_empty():
|
||||
d.condition_as_text = expression_text
|
||||
|
||||
DMConstants.TYPE_DIALOGUE:
|
||||
d.text = text
|
||||
|
||||
if translation_key != text:
|
||||
d.translation_key = translation_key
|
||||
|
||||
if not character.is_empty():
|
||||
d.character = character
|
||||
if not character_replacements.is_empty():
|
||||
d.character_replacements = character_replacements
|
||||
if not text_replacements.is_empty():
|
||||
d.text_replacements = text_replacements
|
||||
if not tags.is_empty():
|
||||
d.tags = tags
|
||||
if not notes.is_empty():
|
||||
d.notes = notes
|
||||
if not siblings.is_empty():
|
||||
d.siblings = siblings
|
||||
if not concurrent_lines.is_empty():
|
||||
d.concurrent_lines = concurrent_lines
|
||||
|
||||
return d
|
||||
|
||||
|
||||
#endregion
|
1
addons/dialogue_manager/compiler/compiled_line.gd.uid
Normal file
1
addons/dialogue_manager/compiler/compiled_line.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dg8j5hudp4210
|
51
addons/dialogue_manager/compiler/compiler.gd
vendored
Normal file
51
addons/dialogue_manager/compiler/compiler.gd
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
## A compiler of Dialogue Manager dialogue.
|
||||
class_name DMCompiler extends RefCounted
|
||||
|
||||
|
||||
## Compile a dialogue script.
|
||||
static func compile_string(text: String, path: String) -> DMCompilerResult:
|
||||
var compilation: DMCompilation = DMCompilation.new()
|
||||
compilation.compile(text, path)
|
||||
|
||||
var result: DMCompilerResult = DMCompilerResult.new()
|
||||
result.imported_paths = compilation.imported_paths
|
||||
result.using_states = compilation.using_states
|
||||
result.character_names = compilation.character_names
|
||||
result.titles = compilation.titles
|
||||
result.first_title = compilation.first_title
|
||||
result.errors = compilation.errors
|
||||
result.lines = compilation.data
|
||||
result.raw_text = text
|
||||
|
||||
return result
|
||||
|
||||
|
||||
## Get the line type of a string. The returned string will match one of the [code]TYPE_[/code] constants of [DMConstants].
|
||||
static func get_line_type(text: String) -> String:
|
||||
var compilation: DMCompilation = DMCompilation.new()
|
||||
return compilation.get_line_type(text)
|
||||
|
||||
|
||||
## Get the static line ID (eg. [code][ID:SOMETHING][/code]) of some text.
|
||||
static func get_static_line_id(text: String) -> String:
|
||||
var compilation: DMCompilation = DMCompilation.new()
|
||||
return compilation.extract_static_line_id(text)
|
||||
|
||||
|
||||
## Get the translatable part of a line.
|
||||
static func extract_translatable_string(text: String) -> String:
|
||||
var compilation: DMCompilation = DMCompilation.new()
|
||||
|
||||
var tree_line = DMTreeLine.new("")
|
||||
tree_line.text = text
|
||||
var line: DMCompiledLine = DMCompiledLine.new("", compilation.get_line_type(text))
|
||||
compilation.parse_character_and_dialogue(tree_line, line, [tree_line], 0, null)
|
||||
|
||||
return line.text
|
||||
|
||||
|
||||
## Get the known titles in a dialogue script.
|
||||
static func get_titles_in_text(text: String, path: String) -> Dictionary:
|
||||
var compilation: DMCompilation = DMCompilation.new()
|
||||
compilation.build_line_tree(compilation.inject_imported_files(text, path))
|
||||
return compilation.titles
|
1
addons/dialogue_manager/compiler/compiler.gd.uid
Normal file
1
addons/dialogue_manager/compiler/compiler.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://chtfdmr0cqtp4
|
50
addons/dialogue_manager/compiler/compiler_regex.gd
vendored
Normal file
50
addons/dialogue_manager/compiler/compiler_regex.gd
vendored
Normal file
@@ -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_TITLE_REGEX: RegEx = RegEx.create_from_string("^[a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]*$")
|
||||
var BEGINS_WITH_NUMBER_REGEX: RegEx = RegEx.create_from_string("^\\d")
|
||||
var CONDITION_REGEX: RegEx = RegEx.create_from_string("(if|elif|while|else if|match|when) (?<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 TAGS_REGEX: RegEx = RegEx.create_from_string("\\[#(?<tags>.*?)\\]")
|
||||
|
||||
var REPLACEMENTS_REGEX: RegEx = RegEx.create_from_string("{{(.*?)}}")
|
||||
|
||||
var ALPHA_NUMERIC: RegEx = RegEx.create_from_string("[^a-zA-Z0-9\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]+")
|
||||
|
||||
var TOKEN_DEFINITIONS: Dictionary = {
|
||||
DMConstants.TOKEN_FUNCTION: RegEx.create_from_string("^[a-zA-Z_\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]*\\("),
|
||||
DMConstants.TOKEN_DICTIONARY_REFERENCE: RegEx.create_from_string("^[a-zA-Z_\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}][a-zA-Z_0-9\\p{Emoji_Presentation}\\p{Han}\\p{Katakana}\\p{Hiragana}\\p{Cyrillic}]*\\["),
|
||||
DMConstants.TOKEN_PARENS_OPEN: RegEx.create_from_string("^\\("),
|
||||
DMConstants.TOKEN_PARENS_CLOSE: RegEx.create_from_string("^\\)"),
|
||||
DMConstants.TOKEN_BRACKET_OPEN: RegEx.create_from_string("^\\["),
|
||||
DMConstants.TOKEN_BRACKET_CLOSE: RegEx.create_from_string("^\\]"),
|
||||
DMConstants.TOKEN_BRACE_OPEN: RegEx.create_from_string("^\\{"),
|
||||
DMConstants.TOKEN_BRACE_CLOSE: RegEx.create_from_string("^\\}"),
|
||||
DMConstants.TOKEN_COLON: RegEx.create_from_string("^:"),
|
||||
DMConstants.TOKEN_COMPARISON: RegEx.create_from_string("^(==|<=|>=|<|>|!=|in )"),
|
||||
DMConstants.TOKEN_ASSIGNMENT: RegEx.create_from_string("^(\\+=|\\-=|\\*=|/=|=)"),
|
||||
DMConstants.TOKEN_NUMBER: RegEx.create_from_string("^\\-?\\d+(\\.\\d+)?"),
|
||||
DMConstants.TOKEN_OPERATOR: RegEx.create_from_string("^(\\+|\\-|\\*|/|%)"),
|
||||
DMConstants.TOKEN_COMMA: RegEx.create_from_string("^,"),
|
||||
DMConstants.TOKEN_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)")
|
||||
}
|
1
addons/dialogue_manager/compiler/compiler_regex.gd.uid
Normal file
1
addons/dialogue_manager/compiler/compiler_regex.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://d3tvcrnicjibp
|
27
addons/dialogue_manager/compiler/compiler_result.gd
vendored
Normal file
27
addons/dialogue_manager/compiler/compiler_result.gd
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
## The result of using the [DMCompiler] to compile some dialogue.
|
||||
class_name DMCompilerResult extends RefCounted
|
||||
|
||||
|
||||
## Any paths that were imported into the compiled dialogue file.
|
||||
var imported_paths: PackedStringArray = []
|
||||
|
||||
## Any "using" directives.
|
||||
var using_states: PackedStringArray = []
|
||||
|
||||
## All titles in the file and the line they point to.
|
||||
var titles: Dictionary = {}
|
||||
|
||||
## The first title in the file.
|
||||
var first_title: String = ""
|
||||
|
||||
## All character names.
|
||||
var character_names: PackedStringArray = []
|
||||
|
||||
## Any compilation errors.
|
||||
var errors: Array[Dictionary] = []
|
||||
|
||||
## A map of all compiled lines.
|
||||
var lines: Dictionary = {}
|
||||
|
||||
## The raw dialogue text.
|
||||
var raw_text: String = ""
|
1
addons/dialogue_manager/compiler/compiler_result.gd.uid
Normal file
1
addons/dialogue_manager/compiler/compiler_result.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dmk74tknimqvg
|
529
addons/dialogue_manager/compiler/expression_parser.gd
vendored
Normal file
529
addons/dialogue_manager/compiler/expression_parser.gd
vendored
Normal file
@@ -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 = _find_match(text)
|
||||
if found.size() > 0:
|
||||
tokens.append({
|
||||
index = index,
|
||||
type = found.type,
|
||||
value = found.value
|
||||
})
|
||||
index += found.value.length()
|
||||
text = found.remaining_text
|
||||
elif text.begins_with(" "):
|
||||
index += 1
|
||||
text = text.substr(1)
|
||||
else:
|
||||
return _build_token_tree_error([], DMConstants.ERR_INVALID_EXPRESSION, index)
|
||||
|
||||
return _build_token_tree(tokens, line_type, "")[0]
|
||||
|
||||
|
||||
## Extract any expressions from some text
|
||||
func extract_replacements(text: String, index: int) -> Array[Dictionary]:
|
||||
var founds: Array[RegExMatch] = regex.REPLACEMENTS_REGEX.search_all(text)
|
||||
|
||||
if founds == null or founds.size() == 0:
|
||||
return []
|
||||
|
||||
var replacements: Array[Dictionary] = []
|
||||
for found in founds:
|
||||
var replacement: Dictionary = {}
|
||||
var value_in_text: String = found.strings[0].substr(0, found.strings[0].length() - 2).substr(2)
|
||||
|
||||
# If there are closing curlie hard-up against the end of a {{...}} block then check for further
|
||||
# curlies just outside of the block.
|
||||
var text_suffix: String = text.substr(found.get_end(0))
|
||||
var expression_suffix: String = ""
|
||||
while text_suffix.begins_with("}"):
|
||||
expression_suffix += "}"
|
||||
text_suffix = text_suffix.substr(1)
|
||||
value_in_text += expression_suffix
|
||||
|
||||
var expression: Array = tokenise(value_in_text, DMConstants.TYPE_DIALOGUE, index + found.get_start(1))
|
||||
if expression.size() == 0:
|
||||
replacement = {
|
||||
index = index + found.get_start(1),
|
||||
error = DMConstants.ERR_INCOMPLETE_EXPRESSION
|
||||
}
|
||||
elif expression[0].type == DMConstants.TYPE_ERROR:
|
||||
replacement = {
|
||||
index = expression[0].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 = 0
|
||||
while tokens.size() > 0 and limit < 1000:
|
||||
limit += 1
|
||||
var token = tokens.pop_front()
|
||||
|
||||
var error = _check_next_token(token, tokens, line_type, expected_close_token)
|
||||
if error != OK:
|
||||
var error_token: Dictionary = tokens[1] if tokens.size() > 1 else token
|
||||
return [_build_token_tree_error(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 = _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 = _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 = _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 = _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 = sub_tree[0]
|
||||
for i in range(0, t.size() - 2):
|
||||
# Convert Lua style dictionaries to string keys
|
||||
if t[i].type == DMConstants.TOKEN_VARIABLE and t[i+1].type == DMConstants.TOKEN_ASSIGNMENT:
|
||||
t[i].type = DMConstants.TOKEN_STRING
|
||||
t[i+1].type = DMConstants.TOKEN_COLON
|
||||
t[i+1].erase("value")
|
||||
|
||||
tree.append({
|
||||
type = DMConstants.TOKEN_DICTIONARY,
|
||||
value = _tokens_to_dictionary(sub_tree[0]),
|
||||
i = token.index
|
||||
})
|
||||
|
||||
tokens = sub_tree[1]
|
||||
|
||||
DMConstants.TOKEN_BRACKET_OPEN:
|
||||
var sub_tree = _build_token_tree(tokens, line_type, DMConstants.TOKEN_BRACKET_CLOSE)
|
||||
|
||||
if sub_tree[0].size() > 0 and sub_tree[0][0].type == DMConstants.TOKEN_ERROR:
|
||||
return [_build_token_tree_error(tree, sub_tree[0][0].value, sub_tree[0][0].i), tokens]
|
||||
|
||||
var type = DMConstants.TOKEN_ARRAY
|
||||
var value = _tokens_to_list(sub_tree[0])
|
||||
|
||||
# See if this is referencing a nested dictionary value
|
||||
if tree.size() > 0:
|
||||
var previous_token = tree[tree.size() - 1]
|
||||
if previous_token.type in [DMConstants.TOKEN_DICTIONARY_REFERENCE, DMConstants.TOKEN_DICTIONARY_NESTED_REFERENCE]:
|
||||
type = DMConstants.TOKEN_DICTIONARY_NESTED_REFERENCE
|
||||
value = value[0]
|
||||
|
||||
tree.append({
|
||||
type = type,
|
||||
value = value,
|
||||
i = token.index
|
||||
})
|
||||
tokens = sub_tree[1]
|
||||
|
||||
DMConstants.TOKEN_PARENS_OPEN:
|
||||
var sub_tree = _build_token_tree(tokens, line_type, DMConstants.TOKEN_PARENS_CLOSE)
|
||||
|
||||
if sub_tree[0].size() > 0 and sub_tree[0][0].type == DMConstants.TOKEN_ERROR:
|
||||
return [_build_token_tree_error(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 = 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 = 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) -> Error:
|
||||
var next_token: Dictionary = { type = null }
|
||||
if next_tokens.size() > 0:
|
||||
next_token = next_tokens.front()
|
||||
|
||||
# Guard for assigning in a condition. If the assignment token isn't inside a Lua dictionary
|
||||
# then it's an unexpected assignment in a condition line.
|
||||
if token.type == DMConstants.TOKEN_ASSIGNMENT and line_type == DMConstants.TYPE_CONDITION and not next_tokens.any(func(t): return t.type == expected_close_token):
|
||||
return DMConstants.ERR_UNEXPECTED_ASSIGNMENT
|
||||
|
||||
# Special case for a negative number after this one
|
||||
if token.type == DMConstants.TOKEN_NUMBER and next_token.type == DMConstants.TOKEN_NUMBER and next_token.value.begins_with("-"):
|
||||
return OK
|
||||
|
||||
var expected_token_types = []
|
||||
var unexpected_token_types = []
|
||||
match token.type:
|
||||
DMConstants.TOKEN_FUNCTION, \
|
||||
DMConstants.TOKEN_PARENS_OPEN:
|
||||
unexpected_token_types = [
|
||||
null,
|
||||
DMConstants.TOKEN_COMMA,
|
||||
DMConstants.TOKEN_COLON,
|
||||
DMConstants.TOKEN_COMPARISON,
|
||||
DMConstants.TOKEN_ASSIGNMENT,
|
||||
DMConstants.TOKEN_OPERATOR,
|
||||
DMConstants.TOKEN_AND_OR,
|
||||
DMConstants.TOKEN_DOT
|
||||
]
|
||||
|
||||
DMConstants.TOKEN_BRACKET_CLOSE:
|
||||
unexpected_token_types = [
|
||||
DMConstants.TOKEN_NOT,
|
||||
DMConstants.TOKEN_BOOL,
|
||||
DMConstants.TOKEN_STRING,
|
||||
DMConstants.TOKEN_NUMBER,
|
||||
DMConstants.TOKEN_VARIABLE
|
||||
]
|
||||
|
||||
DMConstants.TOKEN_BRACE_OPEN:
|
||||
expected_token_types = [
|
||||
DMConstants.TOKEN_STRING,
|
||||
DMConstants.TOKEN_VARIABLE,
|
||||
DMConstants.TOKEN_NUMBER,
|
||||
DMConstants.TOKEN_BRACE_CLOSE
|
||||
]
|
||||
|
||||
DMConstants.TOKEN_PARENS_CLOSE, \
|
||||
DMConstants.TOKEN_BRACE_CLOSE:
|
||||
unexpected_token_types = [
|
||||
DMConstants.TOKEN_NOT,
|
||||
DMConstants.TOKEN_ASSIGNMENT,
|
||||
DMConstants.TOKEN_BOOL,
|
||||
DMConstants.TOKEN_STRING,
|
||||
DMConstants.TOKEN_NUMBER,
|
||||
DMConstants.TOKEN_VARIABLE
|
||||
]
|
||||
|
||||
DMConstants.TOKEN_COMPARISON, \
|
||||
DMConstants.TOKEN_OPERATOR, \
|
||||
DMConstants.TOKEN_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 in tokens:
|
||||
if token.type == DMConstants.TOKEN_COMMA:
|
||||
list.append(current_item)
|
||||
current_item = []
|
||||
else:
|
||||
current_item.append(token)
|
||||
|
||||
if current_item.size() > 0:
|
||||
list.append(current_item)
|
||||
|
||||
return list
|
||||
|
||||
|
||||
# Convert a series of key/value tokens into a [Dictionary]
|
||||
func _tokens_to_dictionary(tokens: Array[Dictionary]) -> Dictionary:
|
||||
var dictionary = {}
|
||||
for i in range(0, tokens.size()):
|
||||
if tokens[i].type == DMConstants.TOKEN_COLON:
|
||||
if tokens.size() == i + 2:
|
||||
dictionary[tokens[i - 1]] = tokens[i + 1]
|
||||
else:
|
||||
dictionary[tokens[i - 1]] = { type = DMConstants.TOKEN_GROUP, value = tokens.slice(i + 1), i = tokens[0].i }
|
||||
|
||||
return dictionary
|
||||
|
||||
|
||||
# Work out what the next token is from a string.
|
||||
func _find_match(input: String) -> Dictionary:
|
||||
for key in regex.TOKEN_DEFINITIONS.keys():
|
||||
var regex = regex.TOKEN_DEFINITIONS.get(key)
|
||||
var found = regex.search(input)
|
||||
if found:
|
||||
return {
|
||||
type = key,
|
||||
remaining_text = input.substr(found.strings[0].length()),
|
||||
value = found.strings[0]
|
||||
}
|
||||
|
||||
return {}
|
||||
|
||||
|
||||
#endregion
|
@@ -0,0 +1 @@
|
||||
uid://dbi4hbar8ubwu
|
68
addons/dialogue_manager/compiler/resolved_goto_data.gd
vendored
Normal file
68
addons/dialogue_manager/compiler/resolved_goto_data.gd
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
## Data associated with a dialogue jump/goto line.
|
||||
class_name DMResolvedGotoData extends RefCounted
|
||||
|
||||
|
||||
## The title that was specified
|
||||
var title: String = ""
|
||||
## The target line's ID
|
||||
var next_id: String = ""
|
||||
## An expression to determine the target line at runtime.
|
||||
var expression: Array[Dictionary] = []
|
||||
## The given line text with the jump syntax removed.
|
||||
var text_without_goto: String = ""
|
||||
## Whether this is a jump-and-return style jump.
|
||||
var is_snippet: bool = false
|
||||
## A parse error if there was one.
|
||||
var error: int
|
||||
## The index in the string where
|
||||
var index: int = 0
|
||||
|
||||
# An instance of the compiler [RegEx] list.
|
||||
var regex: DMCompilerRegEx = DMCompilerRegEx.new()
|
||||
|
||||
|
||||
func _init(text: String, titles: Dictionary) -> void:
|
||||
if not "=> " in text and not "=>< " in text: return
|
||||
|
||||
if "=> " in text:
|
||||
text_without_goto = text.substr(0, text.find("=> ")).strip_edges()
|
||||
elif "=>< " in text:
|
||||
is_snippet = true
|
||||
text_without_goto = text.substr(0, text.find("=>< ")).strip_edges()
|
||||
|
||||
var found: RegExMatch = regex.GOTO_REGEX.search(text)
|
||||
if found == null:
|
||||
return
|
||||
|
||||
title = found.strings[found.names.goto].strip_edges()
|
||||
index = found.get_start(0)
|
||||
|
||||
if title == "":
|
||||
error = DMConstants.ERR_UNKNOWN_TITLE
|
||||
return
|
||||
|
||||
# "=> END!" means end the conversation, ignoring any "=><" chains.
|
||||
if title == "END!":
|
||||
next_id = DMConstants.ID_END_CONVERSATION
|
||||
|
||||
# "=> END" means end the current title (and go back to the previous one if there is one
|
||||
# in the stack)
|
||||
elif title == "END":
|
||||
next_id = DMConstants.ID_END
|
||||
|
||||
elif titles.has(title):
|
||||
next_id = titles.get(title)
|
||||
elif title.begins_with("{{"):
|
||||
var expression_parser: DMExpressionParser = DMExpressionParser.new()
|
||||
var title_expression: Array[Dictionary] = expression_parser.extract_replacements(title, 0)
|
||||
if title_expression[0].has("error"):
|
||||
error = title_expression[0].error
|
||||
else:
|
||||
expression = title_expression[0].expression
|
||||
else:
|
||||
next_id = title
|
||||
error = DMConstants.ERR_UNKNOWN_TITLE
|
||||
|
||||
|
||||
func _to_string() -> String:
|
||||
return "%s =>%s %s (%s)" % [text_without_goto, "<" if is_snippet else "", title, next_id]
|
@@ -0,0 +1 @@
|
||||
uid://llhl5pt47eoq
|
167
addons/dialogue_manager/compiler/resolved_line_data.gd
vendored
Normal file
167
addons/dialogue_manager/compiler/resolved_line_data.gd
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
## Any data associated with inline dialogue BBCodes.
|
||||
class_name DMResolvedLineData extends RefCounted
|
||||
|
||||
## The line's text
|
||||
var text: String = ""
|
||||
## A map of pauses against where they are found in the text.
|
||||
var pauses: Dictionary = {}
|
||||
## A map of speed changes against where they are found in the text.
|
||||
var speeds: Dictionary = {}
|
||||
## A list of any mutations to run and where they are found in the text.
|
||||
var mutations: Array[Array] = []
|
||||
## A duration reference for the line. Represented as "auto" or a stringified number.
|
||||
var time: String = ""
|
||||
|
||||
|
||||
func _init(line: String) -> void:
|
||||
text = line
|
||||
pauses = {}
|
||||
speeds = {}
|
||||
mutations = []
|
||||
time = ""
|
||||
|
||||
var bbcodes: Array = []
|
||||
|
||||
# Remove any escaped brackets (ie. "\[")
|
||||
var escaped_open_brackets: PackedInt32Array = []
|
||||
var escaped_close_brackets: PackedInt32Array = []
|
||||
for i in range(0, text.length() - 1):
|
||||
if text.substr(i, 2) == "\\[":
|
||||
text = text.substr(0, i) + "!" + text.substr(i + 2)
|
||||
escaped_open_brackets.append(i)
|
||||
elif text.substr(i, 2) == "\\]":
|
||||
text = text.substr(0, i) + "!" + text.substr(i + 2)
|
||||
escaped_close_brackets.append(i)
|
||||
|
||||
# Extract all of the BB codes so that we know the actual text (we could do this easier with
|
||||
# a RichTextLabel but then we'd need to await idle_frame which is annoying)
|
||||
var bbcode_positions = find_bbcode_positions_in_string(text)
|
||||
var accumulaive_length_offset = 0
|
||||
for position in bbcode_positions:
|
||||
# Ignore our own markers
|
||||
if position.code in ["wait", "speed", "/speed", "do", "do!", "set", "next", "if", "else", "/if"]:
|
||||
continue
|
||||
|
||||
bbcodes.append({
|
||||
bbcode = position.bbcode,
|
||||
start = position.start,
|
||||
offset_start = position.start - accumulaive_length_offset
|
||||
})
|
||||
accumulaive_length_offset += position.bbcode.length()
|
||||
|
||||
for bb in bbcodes:
|
||||
text = text.substr(0, bb.offset_start) + text.substr(bb.offset_start + bb.bbcode.length())
|
||||
|
||||
# Now find any dialogue markers
|
||||
var next_bbcode_position = find_bbcode_positions_in_string(text, false)
|
||||
var limit = 0
|
||||
while next_bbcode_position.size() > 0 and limit < 1000:
|
||||
limit += 1
|
||||
|
||||
var bbcode = next_bbcode_position[0]
|
||||
|
||||
var index = bbcode.start
|
||||
var code = bbcode.code
|
||||
var raw_args = bbcode.raw_args
|
||||
var args = {}
|
||||
if code in ["do", "do!", "set"]:
|
||||
var compilation: DMCompilation = DMCompilation.new()
|
||||
args["value"] = compilation.extract_mutation("%s %s" % [code, raw_args])
|
||||
else:
|
||||
# Could be something like:
|
||||
# "=1.0"
|
||||
# " rate=20 level=10"
|
||||
if raw_args and raw_args[0] == "=":
|
||||
raw_args = "value" + raw_args
|
||||
for pair in raw_args.strip_edges().split(" "):
|
||||
if "=" in pair:
|
||||
var bits = pair.split("=")
|
||||
args[bits[0]] = bits[1]
|
||||
|
||||
match code:
|
||||
"wait":
|
||||
if pauses.has(index):
|
||||
pauses[index] += args.get("value").to_float()
|
||||
else:
|
||||
pauses[index] = args.get("value").to_float()
|
||||
"speed":
|
||||
speeds[index] = args.get("value").to_float()
|
||||
"/speed":
|
||||
speeds[index] = 1.0
|
||||
"do", "do!", "set":
|
||||
mutations.append([index, args.get("value")])
|
||||
"next":
|
||||
time = args.get("value") if args.has("value") else "0"
|
||||
|
||||
# Find any BB codes that are after this index and remove the length from their start
|
||||
var length = bbcode.bbcode.length()
|
||||
for bb in bbcodes:
|
||||
if bb.offset_start > bbcode.start:
|
||||
bb.offset_start -= length
|
||||
bb.start -= length
|
||||
|
||||
# Find any escaped brackets after this that need moving
|
||||
for i in range(0, escaped_open_brackets.size()):
|
||||
if escaped_open_brackets[i] > bbcode.start:
|
||||
escaped_open_brackets[i] -= length
|
||||
for i in range(0, escaped_close_brackets.size()):
|
||||
if escaped_close_brackets[i] > bbcode.start:
|
||||
escaped_close_brackets[i] -= length
|
||||
|
||||
text = text.substr(0, index) + text.substr(index + length)
|
||||
next_bbcode_position = find_bbcode_positions_in_string(text, false)
|
||||
|
||||
# Put the BB Codes back in
|
||||
for bb in bbcodes:
|
||||
text = text.insert(bb.start, bb.bbcode)
|
||||
|
||||
# Put the escaped brackets back in
|
||||
for index in escaped_open_brackets:
|
||||
text = text.left(index) + "[" + text.right(text.length() - index - 1)
|
||||
for index in escaped_close_brackets:
|
||||
text = text.left(index) + "]" + text.right(text.length() - index - 1)
|
||||
|
||||
|
||||
func find_bbcode_positions_in_string(string: String, find_all: bool = true, include_conditions: bool = false) -> Array[Dictionary]:
|
||||
if not "[" in string: return []
|
||||
|
||||
var positions: Array[Dictionary] = []
|
||||
|
||||
var open_brace_count: int = 0
|
||||
var start: int = 0
|
||||
var bbcode: String = ""
|
||||
var code: String = ""
|
||||
var is_finished_code: bool = false
|
||||
for i in range(0, string.length()):
|
||||
if string[i] == "[":
|
||||
if open_brace_count == 0:
|
||||
start = i
|
||||
bbcode = ""
|
||||
code = ""
|
||||
is_finished_code = false
|
||||
open_brace_count += 1
|
||||
|
||||
else:
|
||||
if not is_finished_code and (string[i].to_upper() != string[i] or string[i] == "/" or string[i] == "!"):
|
||||
code += string[i]
|
||||
else:
|
||||
is_finished_code = true
|
||||
|
||||
if open_brace_count > 0:
|
||||
bbcode += string[i]
|
||||
|
||||
if string[i] == "]":
|
||||
open_brace_count -= 1
|
||||
if open_brace_count == 0 and (include_conditions or not code in ["if", "else", "/if"]):
|
||||
positions.append({
|
||||
bbcode = bbcode,
|
||||
code = code,
|
||||
start = start,
|
||||
end = i,
|
||||
raw_args = bbcode.substr(code.length() + 1, bbcode.length() - code.length() - 2).strip_edges()
|
||||
})
|
||||
|
||||
if not find_all:
|
||||
return positions
|
||||
|
||||
return positions
|
@@ -0,0 +1 @@
|
||||
uid://0k6q8kukq0qa
|
26
addons/dialogue_manager/compiler/resolved_tag_data.gd
vendored
Normal file
26
addons/dialogue_manager/compiler/resolved_tag_data.gd
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
## Tag data associated with a line of dialogue.
|
||||
class_name DMResolvedTagData extends RefCounted
|
||||
|
||||
|
||||
## The list of tags.
|
||||
var tags: PackedStringArray = []
|
||||
## The line with any tag syntax removed.
|
||||
var text_without_tags: String = ""
|
||||
|
||||
# An instance of the compiler [RegEx].
|
||||
var regex: DMCompilerRegEx = DMCompilerRegEx.new()
|
||||
|
||||
|
||||
func _init(text: String) -> void:
|
||||
var resolved_tags: PackedStringArray = []
|
||||
var tag_matches: Array[RegExMatch] = regex.TAGS_REGEX.search_all(text)
|
||||
for tag_match in tag_matches:
|
||||
text = text.replace(tag_match.get_string(), "")
|
||||
var tags = tag_match.get_string().replace("[#", "").replace("]", "").replace(", ", ",").split(",")
|
||||
for tag in tags:
|
||||
tag = tag.replace("#", "")
|
||||
if not tag in resolved_tags:
|
||||
resolved_tags.append(tag)
|
||||
|
||||
tags = resolved_tags
|
||||
text_without_tags = text
|
@@ -0,0 +1 @@
|
||||
uid://cqai3ikuilqfq
|
46
addons/dialogue_manager/compiler/tree_line.gd
vendored
Normal file
46
addons/dialogue_manager/compiler/tree_line.gd
vendored
Normal file
@@ -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 = []
|
||||
tabs.resize(indent)
|
||||
tabs.fill("\t")
|
||||
tabs = "".join(tabs)
|
||||
|
||||
return tabs.join([tabs + "{\n",
|
||||
"\tid: %s\n" % [id],
|
||||
"\ttype: %s\n" % [type],
|
||||
"\tis_random: %s\n" % ["true" if is_random else "false"],
|
||||
"\ttext: %s\n" % [text],
|
||||
"\tnotes: %s\n" % [notes],
|
||||
"\tchildren: []\n" if children.size() == 0 else "\tchildren: [\n" + ",\n".join(children.map(func(child): return str(child))) + "]\n",
|
||||
"}"])
|
1
addons/dialogue_manager/compiler/tree_line.gd.uid
Normal file
1
addons/dialogue_manager/compiler/tree_line.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dsu4i84dpif14
|
Reference in New Issue
Block a user