Some changes
This commit is contained in:
@@ -0,0 +1,507 @@
|
||||
extends Object
|
||||
|
||||
|
||||
const DialogueConstants = preload("../constants.gd")
|
||||
|
||||
const SUPPORTED_BUILTIN_TYPES = [
|
||||
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, with_method: String = "") -> bool:
|
||||
if not typeof(thing) in SUPPORTED_BUILTIN_TYPES: return false
|
||||
|
||||
# If given a Dictionary and a method then make sure it's a known Dictionary method.
|
||||
if typeof(thing) == TYPE_DICTIONARY and with_method != "":
|
||||
return with_method in [
|
||||
&"clear",
|
||||
&"duplicate",
|
||||
&"erase",
|
||||
&"find_key",
|
||||
&"get",
|
||||
&"get_or_add",
|
||||
&"has",
|
||||
&"has_all",
|
||||
&"hash",
|
||||
&"is_empty",
|
||||
&"is_read_only",
|
||||
&"keys",
|
||||
&"make_read_only",
|
||||
&"merge",
|
||||
&"merged",
|
||||
&"recursive_equal",
|
||||
&"size",
|
||||
&"values"]
|
||||
|
||||
return true
|
||||
|
||||
|
||||
static func resolve_property(builtin, property: String):
|
||||
match typeof(builtin):
|
||||
TYPE_DICTIONARY:
|
||||
return builtin.get(property)
|
||||
TYPE_ARRAY, TYPE_PACKED_STRING_ARRAY, TYPE_QUATERNION, TYPE_STRING, TYPE_STRING_NAME:
|
||||
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)
|
||||
|
||||
|
||||
static func resolve_method(thing, method_name: String, args: Array):
|
||||
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 in range(0, args.size()):
|
||||
references.append("arg%d" % i)
|
||||
var 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 = 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):
|
||||
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):
|
||||
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):
|
||||
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):
|
||||
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,208 @@
|
||||
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 = lines[i]
|
||||
var found = key_regex.search(line)
|
||||
if found:
|
||||
known_static_ids[found.strings[found.names.get("key")]] = file_path
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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,
|
||||
dependencies = [],
|
||||
errors = []
|
||||
}
|
||||
|
||||
if compile_result != null:
|
||||
_cache[path].dependencies = Array(compile_result.imported_paths).filter(func(d): return d != path)
|
||||
_cache[path].compiled_at = Time.get_ticks_msec()
|
||||
|
||||
queue_updating_dependencies(path)
|
||||
|
||||
|
||||
## Get the file paths in the cache
|
||||
static func get_files() -> PackedStringArray:
|
||||
return _cache.keys()
|
||||
|
||||
|
||||
## 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[Dictionary]) -> 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 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
|
||||
|
||||
_update_dependency_timer.stop()
|
||||
if not _update_dependency_paths.has(of_path):
|
||||
_update_dependency_paths.append(of_path)
|
||||
_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): 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): return Time.get_ticks_msec() - d.get("compiled_at", 0) > 3000) \
|
||||
.map(func(d): 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:
|
||||
_update_dependency_timer.stop()
|
||||
var import_regex: RegEx = RegEx.create_from_string("import \"(?<path>.*?)\"")
|
||||
var file: FileAccess
|
||||
var found_imports: Array[RegExMatch]
|
||||
for path 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 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,288 @@
|
||||
## A collection of utility functions for working with dialogue translations.
|
||||
class_name DMTranslationUtilities extends RefCounted
|
||||
|
||||
|
||||
## Generate 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 = l.substr(l.find(":") + 1)
|
||||
|
||||
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 (file_path.sha1_text().substr(0, 6) + "_" + str(randi() % 1000000).sha1_text().substr(0, 6)).to_upper()
|
||||
|
||||
|
||||
## Export dialogue and responses to CSV.
|
||||
static func export_translations_to_csv(to_path: String, text: String, dialogue_path: String) -> void:
|
||||
var default_locale: String = DMSettings.get_setting(DMSettings.DEFAULT_CSV_LOCALE, "en")
|
||||
|
||||
var file: FileAccess
|
||||
|
||||
# If the file exists, open it first and work out which keys are already in it
|
||||
var existing_csv: Dictionary = {}
|
||||
var delimiter: String = get_delimiter_for_csv(to_path)
|
||||
var column_count: int = 2
|
||||
var default_locale_column: int = 1
|
||||
var character_column: int = -1
|
||||
var notes_column: int = -1
|
||||
if FileAccess.file_exists(to_path):
|
||||
file = FileAccess.open(to_path, FileAccess.READ)
|
||||
var is_first_line = true
|
||||
var line: Array
|
||||
while !file.eof_reached():
|
||||
line = file.get_csv_line(delimiter)
|
||||
if is_first_line:
|
||||
is_first_line = false
|
||||
column_count = line.size()
|
||||
for i in range(1, line.size()):
|
||||
if line[i] == default_locale:
|
||||
default_locale_column = i
|
||||
elif line[i] == "_character":
|
||||
character_column = i
|
||||
elif line[i] == "_notes":
|
||||
notes_column = i
|
||||
|
||||
# Make sure the line isn't empty before adding it
|
||||
if line.size() > 0 and line[0].strip_edges() != "":
|
||||
existing_csv[line[0]] = line
|
||||
|
||||
# The character column wasn't found in the existing file but the setting is turned on
|
||||
if character_column == -1 and DMSettings.get_setting(DMSettings.INCLUDE_CHARACTER_IN_TRANSLATION_EXPORTS, false):
|
||||
character_column = column_count
|
||||
column_count += 1
|
||||
existing_csv["keys"].append("_character")
|
||||
|
||||
# The notes column wasn't found in the existing file but the setting is turned on
|
||||
if notes_column == -1 and DMSettings.get_setting(DMSettings.INCLUDE_NOTES_IN_TRANSLATION_EXPORTS, false):
|
||||
notes_column = column_count
|
||||
column_count += 1
|
||||
existing_csv["keys"].append("_notes")
|
||||
|
||||
# Start a new file
|
||||
file = FileAccess.open(to_path, FileAccess.WRITE)
|
||||
|
||||
if not FileAccess.file_exists(to_path):
|
||||
var headings: PackedStringArray = ["keys", default_locale] + DMSettings.get_setting(DMSettings.EXTRA_CSV_LOCALES, [])
|
||||
if DMSettings.get_setting(DMSettings.INCLUDE_CHARACTER_IN_TRANSLATION_EXPORTS, false):
|
||||
character_column = headings.size()
|
||||
headings.append("_character")
|
||||
if DMSettings.get_setting(DMSettings.INCLUDE_NOTES_IN_TRANSLATION_EXPORTS, false):
|
||||
notes_column = headings.size()
|
||||
headings.append("_notes")
|
||||
|
||||
file.store_csv_line(headings, delimiter)
|
||||
column_count = headings.size()
|
||||
|
||||
# Write our translations to file
|
||||
var known_keys: PackedStringArray = []
|
||||
|
||||
var dialogue = DMCompiler.compile_string(text, dialogue_path).lines
|
||||
|
||||
# Make a list of stuff that needs to go into the file
|
||||
var lines_to_save = []
|
||||
for key in dialogue.keys():
|
||||
var line: Dictionary = dialogue.get(key)
|
||||
|
||||
if not line.type in [DMConstants.TYPE_DIALOGUE, DMConstants.TYPE_RESPONSE]: continue
|
||||
|
||||
var translation_key: String = line.get(&"translation_key", line.text)
|
||||
|
||||
if translation_key in known_keys: continue
|
||||
|
||||
known_keys.append(translation_key)
|
||||
|
||||
var line_to_save: PackedStringArray = []
|
||||
if existing_csv.has(translation_key):
|
||||
line_to_save = existing_csv.get(translation_key)
|
||||
line_to_save.resize(column_count)
|
||||
existing_csv.erase(translation_key)
|
||||
else:
|
||||
line_to_save.resize(column_count)
|
||||
line_to_save[0] = translation_key
|
||||
|
||||
line_to_save[default_locale_column] = line.text
|
||||
if character_column > -1:
|
||||
line_to_save[character_column] = "(response)" if line.type == DMConstants.TYPE_RESPONSE else line.character
|
||||
if notes_column > -1:
|
||||
line_to_save[notes_column] = line.get("notes", "")
|
||||
|
||||
lines_to_save.append(line_to_save)
|
||||
|
||||
# Store lines in the file, starting with anything that already exists that hasn't been touched
|
||||
for line in existing_csv.values():
|
||||
file.store_csv_line(line, delimiter)
|
||||
for line in lines_to_save:
|
||||
file.store_csv_line(line, delimiter)
|
||||
|
||||
file.close()
|
||||
|
||||
|
||||
## Get the delimier used for an existing CSV
|
||||
static func get_delimiter_for_csv(path: String) -> String:
|
||||
if FileAccess.file_exists(path):
|
||||
var import_path: String = "%s.%s" % [path, "import"]
|
||||
var import_file: ConfigFile = ConfigFile.new()
|
||||
if import_file.load(import_path) == OK:
|
||||
match import_file.get_value("params", "delimier", 0):
|
||||
0:
|
||||
return ","
|
||||
1:
|
||||
return ";"
|
||||
2:
|
||||
return "\t"
|
||||
|
||||
match DMSettings.get_setting(DMSettings.DEFAULT_CSV_DELIMITER, "Comma"):
|
||||
"Comma":
|
||||
return ","
|
||||
"Semicolon":
|
||||
return ";"
|
||||
"Tab":
|
||||
return "\t"
|
||||
|
||||
return ","
|
||||
|
||||
|
||||
## Save any character names in a file to CSV.
|
||||
static func export_character_names_to_csv(to_path: String, text: String, dialogue_path: String) -> void:
|
||||
var file: FileAccess
|
||||
|
||||
# If the file exists, open it first and work out which keys are already in it
|
||||
var existing_csv = {}
|
||||
var delimiter: String = get_delimiter_for_csv(to_path)
|
||||
var commas = []
|
||||
if FileAccess.file_exists(to_path):
|
||||
file = FileAccess.open(to_path, FileAccess.READ)
|
||||
var is_first_line = true
|
||||
var line: Array
|
||||
while !file.eof_reached():
|
||||
line = file.get_csv_line(delimiter)
|
||||
if is_first_line:
|
||||
is_first_line = false
|
||||
for i in range(2, line.size()):
|
||||
commas.append("")
|
||||
# Make sure the line isn't empty before adding it
|
||||
if line.size() > 0 and line[0].strip_edges() != "":
|
||||
existing_csv[line[0]] = line
|
||||
|
||||
# Start a new file
|
||||
file = FileAccess.open(to_path, FileAccess.WRITE)
|
||||
|
||||
if not file.file_exists(to_path):
|
||||
file.store_csv_line(["keys", DMSettings.get_setting(DMSettings.DEFAULT_CSV_LOCALE, "en")], delimiter)
|
||||
|
||||
# Write our translations to file
|
||||
var known_keys: PackedStringArray = []
|
||||
|
||||
var character_names: PackedStringArray = DMCompiler.compile_string(text, dialogue_path).character_names
|
||||
|
||||
# Make a list of stuff that needs to go into the file
|
||||
var lines_to_save = []
|
||||
for character_name in character_names:
|
||||
if character_name in known_keys: continue
|
||||
|
||||
known_keys.append(character_name)
|
||||
|
||||
if existing_csv.has(character_name):
|
||||
var existing_line = existing_csv.get(character_name)
|
||||
existing_line[1] = character_name
|
||||
lines_to_save.append(existing_line)
|
||||
existing_csv.erase(character_name)
|
||||
else:
|
||||
lines_to_save.append(PackedStringArray([character_name, character_name] + commas))
|
||||
|
||||
# Store lines in the file, starting with anything that already exists that hasn't been touched
|
||||
for line in existing_csv.values():
|
||||
file.store_csv_line(line, delimiter)
|
||||
for line in lines_to_save:
|
||||
file.store_csv_line(line, delimiter)
|
||||
|
||||
file.close()
|
||||
|
||||
|
||||
## Replace translatable lines in some text using an existing CSV.
|
||||
static func import_translations_from_csv(from_path: String, text: String) -> String:
|
||||
if not FileAccess.file_exists(from_path): return text
|
||||
|
||||
# Open the CSV file and build a dictionary of the known keys
|
||||
var delimiter: String = get_delimiter_for_csv(from_path)
|
||||
var keys: Dictionary = {}
|
||||
var file: FileAccess = FileAccess.open(from_path, FileAccess.READ)
|
||||
var csv_line: Array
|
||||
while !file.eof_reached():
|
||||
csv_line = file.get_csv_line(delimiter)
|
||||
if csv_line.size() > 1:
|
||||
keys[csv_line[0]] = csv_line[1]
|
||||
|
||||
# Now look over each line in the dialogue and replace the content for matched keys
|
||||
var lines: PackedStringArray = text.split("\n")
|
||||
var start_index: int = 0
|
||||
var end_index: int = 0
|
||||
for i in range(0, lines.size()):
|
||||
var line: String = lines[i]
|
||||
var translation_key: String = DMCompiler.get_static_line_id(line)
|
||||
if keys.has(translation_key):
|
||||
if DMCompiler.get_line_type(line) == DMConstants.TYPE_DIALOGUE:
|
||||
start_index = 0
|
||||
# See if we need to skip over a character name
|
||||
line = line.replace("\\:", "!ESCAPED_COLON!")
|
||||
if ": " in line:
|
||||
start_index = line.find(": ") + 2
|
||||
lines[i] = (line.substr(0, start_index) + keys.get(translation_key) + " [ID:" + translation_key + "]").replace("!ESCAPED_COLON!", ":")
|
||||
|
||||
elif DMCompiler.get_line_type(line) == DMConstants.TYPE_RESPONSE:
|
||||
start_index = line.find("- ") + 2
|
||||
# See if we need to skip over a character name
|
||||
line = line.replace("\\:", "!ESCAPED_COLON!")
|
||||
if ": " in line:
|
||||
start_index = line.find(": ") + 2
|
||||
end_index = line.length()
|
||||
if " =>" in line:
|
||||
end_index = line.find(" =>")
|
||||
if " [if " in line:
|
||||
end_index = line.find(" [if ")
|
||||
lines[i] = (line.substr(0, start_index) + keys.get(translation_key) + " [ID:" + translation_key + "]" + line.substr(end_index)).replace("!ESCAPED_COLON!", ":")
|
||||
|
||||
return "\n".join(lines)
|
||||
@@ -0,0 +1 @@
|
||||
uid://c74q663mmfyk1
|
||||
@@ -0,0 +1,20 @@
|
||||
class_name DMWaiter extends Node
|
||||
|
||||
|
||||
signal waited()
|
||||
|
||||
|
||||
var _actions: PackedStringArray
|
||||
var _null: String = str(null)
|
||||
|
||||
|
||||
func _init(target_actions: PackedStringArray) -> void:
|
||||
_actions = target_actions
|
||||
|
||||
|
||||
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()
|
||||
waited.emit()
|
||||
@@ -0,0 +1 @@
|
||||
uid://bx7dtro7ywali
|
||||
Reference in New Issue
Block a user