Envoi sur GitHub du projet updated

This commit is contained in:
Maurane
2026-01-07 12:47:48 +01:00
parent 34e887afd2
commit 3f1863f0c1
434 changed files with 16021 additions and 5685 deletions
+35 -2
View File
@@ -22,8 +22,32 @@ const SUPPORTED_BUILTIN_TYPES = [
static var resolve_method_error: Error = OK
static func is_supported(thing) -> bool:
return typeof(thing) in SUPPORTED_BUILTIN_TYPES
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):
@@ -407,6 +431,15 @@ static func resolve_vector2_property(vector: Vector2, property: String):
"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]
@@ -0,0 +1 @@
uid://bnfhuubdv5k20
@@ -0,0 +1,171 @@
class_name DMCache extends Node
signal file_content_changed(path: String, new_content: String)
# Keep track of errors and dependencies
# {
# <dialogue file path> = {
# path = <dialogue file path>,
# dependencies = [<dialogue file path>, <dialogue file path>],
# errors = [<error>, <error>]
# }
# }
var _cache: Dictionary = {}
var _update_dependency_timer: Timer = Timer.new()
var _update_dependency_paths: PackedStringArray = []
var _files_marked_for_reimport: PackedStringArray = []
func _ready() -> void:
add_child(_update_dependency_timer)
_update_dependency_timer.timeout.connect(_on_update_dependency_timeout)
_build_cache()
func mark_files_for_reimport(files: PackedStringArray) -> void:
for file in files:
if not _files_marked_for_reimport.has(file):
_files_marked_for_reimport.append(file)
func reimport_files(and_files: PackedStringArray = []) -> void:
for file 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
EditorInterface.get_resource_filesystem().reimport_files(_files_marked_for_reimport)
_files_marked_for_reimport.clear()
## Add a dialogue file to the cache.
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()
# If this is a fresh cache entry, check for dependencies
if compile_result == null and not _update_dependency_paths.has(path):
queue_updating_dependencies(path)
## Get the file paths in the cache
func get_files() -> PackedStringArray:
return _cache.keys()
## Check if a file is known to the cache
func has_file(path: String) -> bool:
return _cache.has(path)
## Remember any errors in a dialogue file
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
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
func queue_updating_dependencies(of_path: String) -> void:
_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
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
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
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)
# Build the initial cache for dialogue files
func _build_cache() -> void:
var current_files: PackedStringArray = _get_dialogue_files_in_filesystem()
for file in current_files:
add_file(file)
# Recursively find any dialogue files in a directory
func _get_dialogue_files_in_filesystem(path: String = "res://") -> PackedStringArray:
var files: PackedStringArray = []
if DirAccess.dir_exists_absolute(path):
var dir = DirAccess.open(path)
dir.list_dir_begin()
var file_name = 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
func _on_update_dependency_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,312 @@
## A collection of utility functions for working with dialogue translations.
class_name DMTranslationUtilities extends RefCounted
## Generate translation keys from some text.
static func generate_translation_keys(text: String) -> String:
var rng: RandomNumberGenerator = RandomNumberGenerator.new()
rng.randomize()
var lines: PackedStringArray = text.split("\n")
var key_regex = RegEx.new()
key_regex.compile("\\[ID:(?<key>.*?)\\]")
var compiled_lines: Dictionary = DMCompiler.compile_string(text, "").lines
# Make list of known keys
var known_keys = {}
for i in range(0, lines.size()):
var line = lines[i]
var found = key_regex.search(line)
if found:
var translatable_text: String = ""
var l = line.replace(found.strings[0], "").strip_edges().strip_edges()
if l.begins_with("- "):
translatable_text = DMCompiler.extract_translatable_string(l)
elif ":" in l:
translatable_text = l.split(":")[1]
else:
translatable_text = l
known_keys[found.strings[found.names.get("key")]] = translatable_text
# Add in any that are missing
for i in lines.size():
var line = lines[i]
var l = 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 = ""
if known_keys.values().has(translatable_text):
key = known_keys.find_key(translatable_text)
else:
var regex: DMCompilerRegEx = DMCompilerRegEx.new()
if DMSettings.get_setting(DMSettings.USE_UUID_ONLY_FOR_IDS, false):
# Generate UUID only
var uuid = str(randi() % 1000000).sha1_text().substr(0, 12)
key = uuid.to_upper()
else:
# Generate text prefix + hash
var prefix_length = DMSettings.get_setting(DMSettings.AUTO_GENERATED_ID_PREFIX_LENGTH, 30)
key = regex.ALPHA_NUMERIC.sub(translatable_text.strip_edges(), "_", true).substr(0, prefix_length)
if key.begins_with("_"):
key = key.substr(1)
if key.ends_with("_"):
key = key.substr(0, key.length() - 1)
# Make sure key is unique
var hashed_key: String = key + "_" + str(randi() % 1000000).sha1_text().substr(0, 6)
while hashed_key in known_keys and translatable_text != known_keys.get(hashed_key):
hashed_key = key + "_" + str(randi() % 1000000).sha1_text().substr(0, 6)
key = hashed_key.to_upper()
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")
known_keys[key] = translatable_text
return "\n".join(lines)
## 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.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