Added dialogue manager plugin

This commit is contained in:
Matthew Barbour
2026-08-30 01:15:04 -05:00
parent e90ea3a0a4
commit f983ab64ce
186 changed files with 20125 additions and 0 deletions
@@ -0,0 +1,532 @@
class_name DMBuiltins extends RefCounted
const SUPPORTED_BUILTIN_TYPES: Array = [
TYPE_STRING,
TYPE_STRING_NAME,
TYPE_ARRAY,
TYPE_PACKED_STRING_ARRAY,
TYPE_VECTOR2,
TYPE_VECTOR3,
TYPE_VECTOR4,
TYPE_DICTIONARY,
TYPE_QUATERNION,
TYPE_COLOR,
TYPE_SIGNAL,
TYPE_CALLABLE
]
static var resolve_method_error: Error = OK
static func is_supported(thing: Variant, with_method: String = "") -> bool:
if not typeof(thing) in SUPPORTED_BUILTIN_TYPES: return false
# If given a Dictionary and a method then make sure it's a known Dictionary method.
if typeof(thing) == TYPE_DICTIONARY and with_method != "":
return with_method in [
&"clear",
&"duplicate",
&"erase",
&"find_key",
&"get",
&"get_or_add",
&"has",
&"has_all",
&"hash",
&"is_empty",
&"is_read_only",
&"keys",
&"make_read_only",
&"merge",
&"merged",
&"recursive_equal",
&"size",
&"values"]
return true
static func resolve_property(builtin: Variant, property: String) -> Variant:
match typeof(builtin):
TYPE_DICTIONARY:
if not builtin.has(property):
match property:
"Count", "Length":
return builtin.size()
"IsEmpty":
return builtin.is_empty()
return builtin.get(property)
TYPE_ARRAY, TYPE_PACKED_STRING_ARRAY:
match property:
"Count", "Length":
return builtin.size()
"IsEmpty":
return builtin.is_empty()
_:
return builtin[property]
TYPE_STRING:
match property:
"Length", "Count":
return builtin.length()
"IsEmpty":
return builtin.is_empty()
_:
return builtin[property]
TYPE_STRING_NAME, TYPE_QUATERNION:
return builtin[property]
# Some types have constants that we need to manually resolve
TYPE_VECTOR2:
return resolve_vector2_property(builtin, property)
TYPE_VECTOR3:
return resolve_vector3_property(builtin, property)
TYPE_VECTOR4:
return resolve_vector4_property(builtin, property)
TYPE_COLOR:
return resolve_color_property(builtin, property)
return null
static func resolve_method(thing: Variant, method_name: String, args: Array) -> Variant:
resolve_method_error = OK
# Resolve static methods manually
match typeof(thing):
TYPE_VECTOR2:
match method_name:
"from_angle":
return Vector2.from_angle(args[0])
TYPE_COLOR:
match method_name:
"from_hsv":
return Color.from_hsv(args[0], args[1], args[2]) if args.size() == 3 else Color.from_hsv(args[0], args[1], args[2], args[3])
"from_ok_hsl":
return Color.from_ok_hsl(args[0], args[1], args[2]) if args.size() == 3 else Color.from_ok_hsl(args[0], args[1], args[2], args[3])
"from_rgbe9995":
return Color.from_rgbe9995(args[0])
"from_string":
return Color.from_string(args[0], args[1])
TYPE_QUATERNION:
match method_name:
"from_euler":
return Quaternion.from_euler(args[0])
# Anything else can be evaulatated automatically
var references: Array = ["thing"]
for i: int in range(0, args.size()):
references.append("arg%d" % i)
var expression: Expression = Expression.new()
if expression.parse("thing.%s(%s)" % [method_name, ",".join(references.slice(1))], references) != OK:
assert(false, expression.get_error_text())
var result: Variant = await expression.execute([thing] + args, null, false)
if expression.has_execute_failed():
resolve_method_error = ERR_CANT_RESOLVE
return null
return result
static func has_resolve_method_failed() -> bool:
return resolve_method_error != OK
static func resolve_color_property(color: Color, property: String) -> Variant:
match property:
"ALICE_BLUE":
return Color.ALICE_BLUE
"ANTIQUE_WHITE":
return Color.ANTIQUE_WHITE
"AQUA":
return Color.AQUA
"AQUAMARINE":
return Color.AQUAMARINE
"AZURE":
return Color.AZURE
"BEIGE":
return Color.BEIGE
"BISQUE":
return Color.BISQUE
"BLACK":
return Color.BLACK
"BLANCHED_ALMOND":
return Color.BLANCHED_ALMOND
"BLUE":
return Color.BLUE
"BLUE_VIOLET":
return Color.BLUE_VIOLET
"BROWN":
return Color.BROWN
"BURLYWOOD":
return Color.BURLYWOOD
"CADET_BLUE":
return Color.CADET_BLUE
"CHARTREUSE":
return Color.CHARTREUSE
"CHOCOLATE":
return Color.CHOCOLATE
"CORAL":
return Color.CORAL
"CORNFLOWER_BLUE":
return Color.CORNFLOWER_BLUE
"CORNSILK":
return Color.CORNSILK
"CRIMSON":
return Color.CRIMSON
"CYAN":
return Color.CYAN
"DARK_BLUE":
return Color.DARK_BLUE
"DARK_CYAN":
return Color.DARK_CYAN
"DARK_GOLDENROD":
return Color.DARK_GOLDENROD
"DARK_GRAY":
return Color.DARK_GRAY
"DARK_GREEN":
return Color.DARK_GREEN
"DARK_KHAKI":
return Color.DARK_KHAKI
"DARK_MAGENTA":
return Color.DARK_MAGENTA
"DARK_OLIVE_GREEN":
return Color.DARK_OLIVE_GREEN
"DARK_ORANGE":
return Color.DARK_ORANGE
"DARK_ORCHID":
return Color.DARK_ORCHID
"DARK_RED":
return Color.DARK_RED
"DARK_SALMON":
return Color.DARK_SALMON
"DARK_SEA_GREEN":
return Color.DARK_SEA_GREEN
"DARK_SLATE_BLUE":
return Color.DARK_SLATE_BLUE
"DARK_SLATE_GRAY":
return Color.DARK_SLATE_GRAY
"DARK_TURQUOISE":
return Color.DARK_TURQUOISE
"DARK_VIOLET":
return Color.DARK_VIOLET
"DEEP_PINK":
return Color.DEEP_PINK
"DEEP_SKY_BLUE":
return Color.DEEP_SKY_BLUE
"DIM_GRAY":
return Color.DIM_GRAY
"DODGER_BLUE":
return Color.DODGER_BLUE
"FIREBRICK":
return Color.FIREBRICK
"FLORAL_WHITE":
return Color.FLORAL_WHITE
"FOREST_GREEN":
return Color.FOREST_GREEN
"FUCHSIA":
return Color.FUCHSIA
"GAINSBORO":
return Color.GAINSBORO
"GHOST_WHITE":
return Color.GHOST_WHITE
"GOLD":
return Color.GOLD
"GOLDENROD":
return Color.GOLDENROD
"GRAY":
return Color.GRAY
"GREEN":
return Color.GREEN
"GREEN_YELLOW":
return Color.GREEN_YELLOW
"HONEYDEW":
return Color.HONEYDEW
"HOT_PINK":
return Color.HOT_PINK
"INDIAN_RED":
return Color.INDIAN_RED
"INDIGO":
return Color.INDIGO
"IVORY":
return Color.IVORY
"KHAKI":
return Color.KHAKI
"LAVENDER":
return Color.LAVENDER
"LAVENDER_BLUSH":
return Color.LAVENDER_BLUSH
"LAWN_GREEN":
return Color.LAWN_GREEN
"LEMON_CHIFFON":
return Color.LEMON_CHIFFON
"LIGHT_BLUE":
return Color.LIGHT_BLUE
"LIGHT_CORAL":
return Color.LIGHT_CORAL
"LIGHT_CYAN":
return Color.LIGHT_CYAN
"LIGHT_GOLDENROD":
return Color.LIGHT_GOLDENROD
"LIGHT_GRAY":
return Color.LIGHT_GRAY
"LIGHT_GREEN":
return Color.LIGHT_GREEN
"LIGHT_PINK":
return Color.LIGHT_PINK
"LIGHT_SALMON":
return Color.LIGHT_SALMON
"LIGHT_SEA_GREEN":
return Color.LIGHT_SEA_GREEN
"LIGHT_SKY_BLUE":
return Color.LIGHT_SKY_BLUE
"LIGHT_SLATE_GRAY":
return Color.LIGHT_SLATE_GRAY
"LIGHT_STEEL_BLUE":
return Color.LIGHT_STEEL_BLUE
"LIGHT_YELLOW":
return Color.LIGHT_YELLOW
"LIME":
return Color.LIME
"LIME_GREEN":
return Color.LIME_GREEN
"LINEN":
return Color.LINEN
"MAGENTA":
return Color.MAGENTA
"MAROON":
return Color.MAROON
"MEDIUM_AQUAMARINE":
return Color.MEDIUM_AQUAMARINE
"MEDIUM_BLUE":
return Color.MEDIUM_BLUE
"MEDIUM_ORCHID":
return Color.MEDIUM_ORCHID
"MEDIUM_PURPLE":
return Color.MEDIUM_PURPLE
"MEDIUM_SEA_GREEN":
return Color.MEDIUM_SEA_GREEN
"MEDIUM_SLATE_BLUE":
return Color.MEDIUM_SLATE_BLUE
"MEDIUM_SPRING_GREEN":
return Color.MEDIUM_SPRING_GREEN
"MEDIUM_TURQUOISE":
return Color.MEDIUM_TURQUOISE
"MEDIUM_VIOLET_RED":
return Color.MEDIUM_VIOLET_RED
"MIDNIGHT_BLUE":
return Color.MIDNIGHT_BLUE
"MINT_CREAM":
return Color.MINT_CREAM
"MISTY_ROSE":
return Color.MISTY_ROSE
"MOCCASIN":
return Color.MOCCASIN
"NAVAJO_WHITE":
return Color.NAVAJO_WHITE
"NAVY_BLUE":
return Color.NAVY_BLUE
"OLD_LACE":
return Color.OLD_LACE
"OLIVE":
return Color.OLIVE
"OLIVE_DRAB":
return Color.OLIVE_DRAB
"ORANGE":
return Color.ORANGE
"ORANGE_RED":
return Color.ORANGE_RED
"ORCHID":
return Color.ORCHID
"PALE_GOLDENROD":
return Color.PALE_GOLDENROD
"PALE_GREEN":
return Color.PALE_GREEN
"PALE_TURQUOISE":
return Color.PALE_TURQUOISE
"PALE_VIOLET_RED":
return Color.PALE_VIOLET_RED
"PAPAYA_WHIP":
return Color.PAPAYA_WHIP
"PEACH_PUFF":
return Color.PEACH_PUFF
"PERU":
return Color.PERU
"PINK":
return Color.PINK
"PLUM":
return Color.PLUM
"POWDER_BLUE":
return Color.POWDER_BLUE
"PURPLE":
return Color.PURPLE
"REBECCA_PURPLE":
return Color.REBECCA_PURPLE
"RED":
return Color.RED
"ROSY_BROWN":
return Color.ROSY_BROWN
"ROYAL_BLUE":
return Color.ROYAL_BLUE
"SADDLE_BROWN":
return Color.SADDLE_BROWN
"SALMON":
return Color.SALMON
"SANDY_BROWN":
return Color.SANDY_BROWN
"SEA_GREEN":
return Color.SEA_GREEN
"SEASHELL":
return Color.SEASHELL
"SIENNA":
return Color.SIENNA
"SILVER":
return Color.SILVER
"SKY_BLUE":
return Color.SKY_BLUE
"SLATE_BLUE":
return Color.SLATE_BLUE
"SLATE_GRAY":
return Color.SLATE_GRAY
"SNOW":
return Color.SNOW
"SPRING_GREEN":
return Color.SPRING_GREEN
"STEEL_BLUE":
return Color.STEEL_BLUE
"TAN":
return Color.TAN
"TEAL":
return Color.TEAL
"THISTLE":
return Color.THISTLE
"TOMATO":
return Color.TOMATO
"TRANSPARENT":
return Color.TRANSPARENT
"TURQUOISE":
return Color.TURQUOISE
"VIOLET":
return Color.VIOLET
"WEB_GRAY":
return Color.WEB_GRAY
"WEB_GREEN":
return Color.WEB_GREEN
"WEB_MAROON":
return Color.WEB_MAROON
"WEB_PURPLE":
return Color.WEB_PURPLE
"WHEAT":
return Color.WHEAT
"WHITE":
return Color.WHITE
"WHITE_SMOKE":
return Color.WHITE_SMOKE
"YELLOW":
return Color.YELLOW
"YELLOW_GREEN":
return Color.YELLOW_GREEN
return color[property]
static func resolve_vector2_property(vector: Vector2, property: String) -> Variant:
match property:
"AXIS_X":
return Vector2.AXIS_X
"AXIS_Y":
return Vector2.AXIS_Y
"ZERO":
return Vector2.ZERO
"ONE":
return Vector2.ONE
"INF":
return Vector2.INF
"LEFT":
return Vector2.LEFT
"RIGHT":
return Vector2.RIGHT
"UP":
return Vector2.UP
"DOWN":
return Vector2.DOWN
"DOWN_LEFT":
return Vector2(-1, 1)
"DOWN_RIGHT":
return Vector2(1, 1)
"UP_LEFT":
return Vector2(-1, -1)
"UP_RIGHT":
return Vector2(1, -1)
return vector[property]
static func resolve_vector3_property(vector: Vector3, property: String) -> Variant:
match property:
"AXIS_X":
return Vector3.AXIS_X
"AXIS_Y":
return Vector3.AXIS_Y
"AXIS_Z":
return Vector3.AXIS_Z
"ZERO":
return Vector3.ZERO
"ONE":
return Vector3.ONE
"INF":
return Vector3.INF
"LEFT":
return Vector3.LEFT
"RIGHT":
return Vector3.RIGHT
"UP":
return Vector3.UP
"DOWN":
return Vector3.DOWN
"FORWARD":
return Vector3.FORWARD
"BACK":
return Vector3.BACK
"MODEL_LEFT":
return Vector3(1, 0, 0)
"MODEL_RIGHT":
return Vector3(-1, 0, 0)
"MODEL_TOP":
return Vector3(0, 1, 0)
"MODEL_BOTTOM":
return Vector3(0, -1, 0)
"MODEL_FRONT":
return Vector3(0, 0, 1)
"MODEL_REAR":
return Vector3(0, 0, -1)
return vector[property]
static func resolve_vector4_property(vector: Vector4, property: String) -> Variant:
match property:
"AXIS_X":
return Vector4.AXIS_X
"AXIS_Y":
return Vector4.AXIS_Y
"AXIS_Z":
return Vector4.AXIS_Z
"AXIS_W":
return Vector4.AXIS_W
"ZERO":
return Vector4.ZERO
"ONE":
return Vector4.ONE
"INF":
return Vector4.INF
return vector[property]
@@ -0,0 +1 @@
uid://bnfhuubdv5k20
@@ -0,0 +1,227 @@
class_name DMCache extends RefCounted
## Keep track of errors and dependencies
## {
## <dialogue file path> = {
## path = <dialogue file path>,
## dependencies = [<dialogue file path>, <dialogue file path>],
## errors = [<error>, <error>]
## }
## }
static var _cache: Dictionary = {}
static var _update_dependency_timer: Timer
static var _update_dependency_paths: PackedStringArray = []
static var _files_marked_for_reimport: PackedStringArray = []
## Keep track of used static IDs
## {
## <static ID> = <file path>
## }
## Before compiling a file, remove any static IDs with a file path that matches
## the file
static var known_static_ids: Dictionary = {}
## Build the initial cache for dialogue files
static func prepare() -> void:
_update_dependency_timer = Timer.new()
_update_dependency_timer.timeout.connect(_on_dependency_timer_timeout)
(Engine.get_main_loop() as SceneTree).root.add_child(_update_dependency_timer)
var current_files: PackedStringArray = _get_dialogue_files_in_filesystem()
for file: String in current_files:
add_file(file)
# Find any static IDs
var key_regex: RegEx = RegEx.create_from_string("\\[ID:(?<key>.*?)\\]")
for file_path: String in get_files():
var text: String = FileAccess.get_file_as_string(file_path)
var lines: PackedStringArray = text.split("\n")
for i: int in range(0, lines.size()):
var line: String = lines[i]
var found: RegExMatch = key_regex.search(line)
if found:
known_static_ids[found.strings[found.names.get("key")]] = file_path
## Add some files to the queue for reimporting.
static func mark_files_for_reimport(files: PackedStringArray) -> void:
for file: String in files:
if not _files_marked_for_reimport.has(file):
_files_marked_for_reimport.append(file)
## Attempt to reimport all known dialogue files.
static func reimport_all_files() -> void:
reimport_files(_get_dialogue_files_in_filesystem())
## Attempt to reimport any files marked for reimporting.
static func reimport_files(and_files: PackedStringArray = []) -> void:
for file: String in and_files:
if not _files_marked_for_reimport.has(file):
_files_marked_for_reimport.append(file)
if _files_marked_for_reimport.is_empty(): return
# Guard against recursive reimport calls. Don't mark for reimport unless attempted once.
var filesystem: Object = Engine.get_singleton("EditorInterface").get_resource_filesystem()
if filesystem.is_scanning():
# Defer the reimport to the next idle frame.
_schedule_deferred_reimport.call_deferred()
return
# Attempt reimport immediately if not busy.
Engine.get_singleton("EditorInterface").get_resource_filesystem().reimport_files(_files_marked_for_reimport)
_files_marked_for_reimport.clear()
## Helper to try and resolve recursive import crashes while importer is busy.
static func _schedule_deferred_reimport() -> void:
# Wait before trying again.
if _files_marked_for_reimport.is_empty(): return
var filesystem: Object = Engine.get_singleton("EditorInterface").get_resource_filesystem()
if filesystem.is_scanning():
# Still working on it. Try again later.
await Engine.get_main_loop().create_timer(0.1).timeout
_schedule_deferred_reimport()
return
filesystem.reimport_files(_files_marked_for_reimport)
_files_marked_for_reimport.clear()
## Add a dialogue file to the cache.
static func add_file(path: String, compile_result: DMCompilerResult = null) -> void:
_cache[path] = {
path = path,
cues = {},
dependencies = [],
errors = []
}
if compile_result != null:
_cache[path].cues = compile_result.cues
_cache[path].dependencies = Array(compile_result.imported_paths).filter(func(d: String) -> bool: return d != path)
_cache[path].compiled_at = Time.get_ticks_msec()
queue_updating_dependencies(path)
## Get the file paths in the cache.
static func get_files() -> PackedStringArray:
return _cache.keys()
## Get the data for a file path.
static func get_file_data(key: String) -> Dictionary:
return _cache.get(key, {})
## Check if a file is known to the cache.
static func has_file(path: String) -> bool:
return _cache.has(path)
## Remember any errors in a dialogue file.
static func add_errors_to_file(path: String, errors: Array[DMError]) -> void:
if _cache.has(path):
_cache[path].errors = errors
else:
_cache[path] = {
path = path,
resource_path = "",
dependencies = [],
errors = errors
}
## Get a list of files that have errors.
static func get_files_with_errors() -> Array[Dictionary]:
var files_with_errors: Array[Dictionary] = []
for dialogue_file: Dictionary in _cache.values():
if dialogue_file and dialogue_file.errors.size() > 0:
files_with_errors.append(dialogue_file)
return files_with_errors
## Queue a file to have its dependencies checked.
static func queue_updating_dependencies(of_path: String) -> void:
if _update_dependency_paths.has(of_path): return
if is_instance_valid(_update_dependency_timer):
_update_dependency_timer.stop()
if not _update_dependency_paths.has(of_path):
_update_dependency_paths.append(of_path)
if is_instance_valid(_update_dependency_timer):
_update_dependency_timer.start(0.5)
## Update any references to a file path that has moved.
static func move_file_path(from_path: String, to_path: String) -> void:
if not _cache.has(from_path): return
if to_path != "":
_cache[to_path] = _cache[from_path].duplicate()
_cache.erase(from_path)
## Get every dialogue file that imports on a file of a given path.
static func get_files_with_dependency(imported_path: String) -> Array:
return _cache.values().filter(func(d: Dictionary) -> bool: return d.dependencies.has(imported_path))
## Get any paths that are dependent on a given path.
static func get_dependent_paths_for_reimport(on_path: String) -> PackedStringArray:
return get_files_with_dependency(on_path) \
.filter(func(d: Dictionary) -> bool: return Time.get_ticks_msec() - d.get("compiled_at", 0) > 3000) \
.map(func(d: Dictionary) -> String: return d.path)
# Recursively find any dialogue files in a directory.
static func _get_dialogue_files_in_filesystem(path: String = "res://") -> PackedStringArray:
var files: PackedStringArray = []
if DirAccess.dir_exists_absolute(path):
var dir: DirAccess = DirAccess.open(path)
dir.list_dir_begin()
var file_name: String = dir.get_next()
while file_name != "":
var file_path: String = (path + "/" + file_name).simplify_path()
if dir.current_is_dir():
if not file_name in [".godot", ".tmp"]:
files.append_array(_get_dialogue_files_in_filesystem(file_path))
elif file_name.get_extension() == "dialogue":
files.append(file_path)
file_name = dir.get_next()
return files
#region Signals
static func _on_dependency_timer_timeout() -> void:
if is_instance_valid(_update_dependency_timer):
_update_dependency_timer.stop()
var import_regex: RegEx = RegEx.create_from_string("import \"(?<path>.*?)\"")
var file: FileAccess
var found_imports: Array[RegExMatch]
for path: String in _update_dependency_paths:
# Open the file and check for any "import" lines
file = FileAccess.open(path, FileAccess.READ)
found_imports = import_regex.search_all(file.get_as_text())
var dependencies: PackedStringArray = []
for found: RegExMatch in found_imports:
dependencies.append(found.strings[found.names.path])
_cache[path].dependencies = dependencies
_update_dependency_paths.clear()
#endregion
@@ -0,0 +1 @@
uid://d3c83yd6bjp43
@@ -0,0 +1,11 @@
class_name DMError extends RefCounted
var line_number: int
var column_number: int
var error: int
func _init(data: Dictionary) -> void:
line_number = data.get("line_number", 0)
column_number = data.get("column_number", 0)
error = data.get("error", DMConstants.ERR_UNKNOWN_ERROR)
@@ -0,0 +1 @@
uid://j53u75yx7iom
@@ -0,0 +1,92 @@
class_name DMThemeValues extends RefCounted
var scale: float = 1
var background_color: Color = Color.WHITE
var current_line_color: Color = Color.WHITE
var error_line_color: Color = Color.WHITE
var critical_color: Color = Color.WHITE
var notice_color: Color = Color.WHITE
var cues_color: Color = Color.WHITE
var text_color: Color = Color.WHITE
var tags_color: Color = Color.WHITE
var conditions_color: Color = Color.WHITE
var mutations_color: Color = Color.WHITE
var mutations_line_color: Color = Color.WHITE
var members_color: Color = Color.WHITE
var strings_color: Color = Color.WHITE
var numbers_color: Color = Color.WHITE
var symbols_color: Color = Color.WHITE
var comments_color: Color = Color.WHITE
var jumps_color: Color = Color.WHITE
var font_size: int = 16
func _init(values: Dictionary) -> void:
scale = values.scale
background_color = values.background_color
current_line_color = values.current_line_color
error_line_color = values.error_line_color
critical_color = values.critical_color
notice_color = values.notice_color
cues_color = values.cues_color
text_color = values.text_color
tags_color = values.tags_color
conditions_color = values.conditions_color
mutations_color = values.mutations_color
mutations_line_color = values.mutations_line_color
members_color = values.members_color
strings_color = values.strings_color
numbers_color = values.numbers_color
symbols_color = values.symbols_color
comments_color = values.comments_color
jumps_color = values.jumps_color
font_size = values.font_size
## Get size and colour values used for setting themes.
static func get_values_from_editor() -> DMThemeValues:
var editor_settings: EditorSettings = EditorInterface.get_editor_settings()
return DMThemeValues.new({
scale = EditorInterface.get_editor_scale(),
background_color = Color(editor_settings.get_setting("interface/theme/base_color").blend(editor_settings.get_setting("text_editor/theme/highlighting/background_color")), 1),
current_line_color = editor_settings.get_setting("text_editor/theme/highlighting/current_line_color"),
error_line_color = editor_settings.get_setting("text_editor/theme/highlighting/mark_color"),
critical_color = editor_settings.get_setting("text_editor/theme/highlighting/comment_markers/critical_color"),
notice_color = editor_settings.get_setting("text_editor/theme/highlighting/comment_markers/notice_color"),
cues_color = editor_settings.get_setting("text_editor/theme/highlighting/gdscript/node_reference_color"),
text_color = editor_settings.get_setting("text_editor/theme/highlighting/text_color"),
tags_color = editor_settings.get_setting("text_editor/theme/highlighting/string_placeholder_color"),
conditions_color = editor_settings.get_setting("text_editor/theme/highlighting/keyword_color"),
mutations_color = editor_settings.get_setting("text_editor/theme/highlighting/function_color"),
mutations_line_color = Color(editor_settings.get_setting("text_editor/theme/highlighting/function_color"), 0.6),
members_color = editor_settings.get_setting("text_editor/theme/highlighting/member_variable_color"),
strings_color = editor_settings.get_setting("text_editor/theme/highlighting/string_color"),
numbers_color = editor_settings.get_setting("text_editor/theme/highlighting/number_color"),
symbols_color = editor_settings.get_setting("text_editor/theme/highlighting/symbol_color"),
comments_color = editor_settings.get_setting("text_editor/theme/highlighting/comment_color"),
jumps_color = Color(editor_settings.get_setting("text_editor/theme/highlighting/gdscript/node_reference_color"), 0.6),
font_size = editor_settings.get_setting("interface/editor/code_font_size")
})
## Return a copy of a texture with a tint applied.
static func get_icon_with_color(icon: Texture2D, color: Color) -> ImageTexture:
var image: Image = icon.get_image().duplicate()
for x: int in image.get_width():
for y: int in image.get_height():
var pixel: Color = image.get_pixel(x, y)
pixel *= color
image.set_pixel(x, y, pixel)
return ImageTexture.create_from_image(image)
@@ -0,0 +1 @@
uid://du8x3k1nth502
@@ -0,0 +1,56 @@
## A collection of utility functions for working with dialogue translations.
class_name DMTranslationUtilities extends RefCounted
## Generate static IDs/translation keys from some text.
static func generate_static_line_ids_for_project() -> void:
var rng: RandomNumberGenerator = RandomNumberGenerator.new()
rng.randomize()
for file_path: String in DMCache.get_files():
var text: String = FileAccess.get_file_as_string(file_path)
text = generate_static_line_ids_for_text(text, file_path)
var file: FileAccess = FileAccess.open(file_path, FileAccess.WRITE)
file.store_string(text)
file.close()
## Generate static line IDs for some text.
static func generate_static_line_ids_for_text(text: String, file_path: String) -> String:
var lines: PackedStringArray = text.split("\n")
var compiled_lines: Dictionary = DMCompiler.compile_string(text, "").lines
# Add in any that are missing
for i: int in lines.size():
var line: String = lines[i]
var l: String = line.strip_edges()
if not [DMConstants.TYPE_DIALOGUE, DMConstants.TYPE_RESPONSE].has(DMCompiler.get_line_type(l)): continue
if not compiled_lines.has(str(i)): continue
if "[ID:" in line: continue
var translatable_text: String = ""
if l.begins_with("- "):
translatable_text = DMCompiler.extract_translatable_string(l)
else:
translatable_text = Array(l.replace("\\:", "!ESCAPED_COLON!").split(":")).back().replace("!ESCAPED_COLON!", "\\:")
var key: String = _generate_id(file_path)
while key in DMCache.known_static_ids:
key = _generate_id(file_path)
line = line.replace("\\n", "!NEWLINE!")
translatable_text = translatable_text.replace("\\n", "!NEWLINE!")
lines[i] = line.replace(translatable_text, translatable_text + " [ID:%s]" % [key]).replace("!NEWLINE!", "\\n")
DMCache.known_static_ids[key] = file_path
return "\n".join(lines)
## Get a random-ish ID for a line.
static func _generate_id(file_path: String) -> String:
return ResourceUID.path_to_uid(file_path).replace("uid://", "") + "_" + str(randi() % 1000000).sha1_text().substr(0, 6)
@@ -0,0 +1 @@
uid://c74q663mmfyk1
@@ -0,0 +1,35 @@
class_name DMWaiter extends Node
signal waited(for_action: String)
static var all_waiters: Array[DMWaiter]
var _actions: PackedStringArray
var _null: String = str(null)
## Emit [code]waited[/code] on all waiting [DMWaiter]s.
static func clear_all() -> void:
for waiter: DMWaiter in all_waiters:
all_waiters.erase(waiter)
waiter.waited.emit(null)
func _init(target_actions: PackedStringArray) -> void:
all_waiters.append(self)
_actions = target_actions
func _exit_tree() -> void:
all_waiters.erase(self)
func _input(event: InputEvent) -> void:
for action: String in _actions:
if event.is_pressed():
if action == _null or (InputMap.has_action(action) and event.is_action(action)):
get_viewport().set_input_as_handled()
all_waiters.erase(self)
waited.emit(action)
@@ -0,0 +1 @@
uid://bx7dtro7ywali