Files
GGJ26/addons/maaacks_game_template/installer/copy_and_edit_files.gd
minimata 44f251ed66
All checks were successful
Create tag and build when new code gets to main / BumpTag (push) Successful in 6s
Create tag and build when new code gets to main / Export (push) Successful in 1m1s
Basic game template addon
2026-01-30 19:45:56 +01:00

176 lines
5.7 KiB
GDScript

@tool
extends Node
## Script for automatically copying Godot scenes and scripts without UIDs.
signal canceled
signal completed(target_path : String)
const UID_PREG_MATCH = r'uid="uid:\/\/[0-9a-z]+" '
const RUNNING_CHECK_DELAY : float = 0.25
const RESAVING_DELAY : float = 1.0
const RAW_COPY_EXTENSIONS : Array = ["gd", "md", "txt"]
const OMIT_COPY_EXTENSIONS : Array = ["uid"]
const REPLACE_CONTENT_EXTENSIONS : Array = ["gd", "tscn", "tres", "md"]
@onready var destination_dialog : FileDialog = $DestinationDialog
@export_dir var relative_path : String :
set(value):
relative_path = value
if not relative_path.ends_with("/"):
relative_path += "/"
@export var replace_strings_map : Dictionary
@export var visible : bool = true :
set(value):
visible = value
if is_inside_tree():
destination_dialog.visible = visible
func show() -> void:
visible = true
func hide() -> void:
visible = false
func close() -> void:
queue_free()
func _remove_uids(content : String) -> String:
var regex = RegEx.new()
regex.compile(UID_PREG_MATCH)
return regex.sub(content, "", true)
func _replace_paths(content : String, target_path : String) -> String:
return content.replace(relative_path.trim_prefix("res://"), target_path.trim_prefix("res://"))
func _replace_strings(content : String) -> String:
for key in replace_strings_map:
var value : String = replace_strings_map[key]
content = content.replace(key, value)
return content
func _replace_content(content : String, target_path : String) -> String:
var replaced_content : String
replaced_content = _remove_uids(content)
replaced_content = _replace_paths(replaced_content, target_path)
replaced_content = _replace_strings(replaced_content)
return replaced_content
func _replace_file_contents(file_path : String, target_path : String) -> void:
var extension : String = file_path.get_extension()
if extension not in REPLACE_CONTENT_EXTENSIONS:
return
var file = FileAccess.open(file_path, FileAccess.READ)
if file == null:
push_error("plugin error - null file: `%s`" % file_path)
return
var original_content = file.get_as_text()
file.close()
var replaced_content := _replace_content(original_content, target_path)
if replaced_content == original_content: return
file = FileAccess.open(file_path, FileAccess.WRITE)
file.store_string(replaced_content)
file.close()
func _save_resource(resource_path : String, resource_destination : String, whitelisted_extensions : PackedStringArray = []) -> Error:
var extension : String = resource_path.get_extension()
if whitelisted_extensions.size() > 0:
if not extension in whitelisted_extensions:
return OK
if extension == "import":
# skip import files
return OK
var file_object = load(resource_path)
if file_object is Resource:
var possible_extensions = ResourceSaver.get_recognized_extensions(file_object)
if possible_extensions.has(extension):
return ResourceSaver.save(file_object, resource_destination, ResourceSaver.FLAG_CHANGE_PATH)
else:
return ERR_FILE_UNRECOGNIZED
else:
return ERR_FILE_UNRECOGNIZED
return OK
func _raw_copy_file_path(file_path : String, destination_path : String) -> Error:
var dir := DirAccess.open("res://")
var error := dir.copy(file_path, destination_path)
return error
func _copy_file_path(file_path : String, destination_path : String, target_path : String) -> Error:
var error : Error
if file_path.get_extension() in OMIT_COPY_EXTENSIONS:
return error
if file_path.get_extension() in RAW_COPY_EXTENSIONS:
error = _raw_copy_file_path(file_path, destination_path)
else:
error = _save_resource(file_path, destination_path)
if error == ERR_FILE_UNRECOGNIZED:
error = _raw_copy_file_path(file_path, destination_path)
if not error:
_replace_file_contents(destination_path, target_path)
return error
func _copy_directory_path(dir_path : String, target_path : String) -> void:
if not dir_path.ends_with("/"):
dir_path += "/"
var dir = DirAccess.open(dir_path)
if dir:
dir.list_dir_begin()
var file_name = dir.get_next()
var error : Error
while file_name != "" and error == 0:
var file_relative_path = dir_path.trim_prefix(relative_path)
var destination_path = target_path + file_relative_path + file_name
var full_file_path = dir_path + file_name
if dir.current_is_dir():
if not dir.dir_exists(destination_path):
error = dir.make_dir(destination_path)
_copy_directory_path(full_file_path, target_path)
else:
error = _copy_file_path(full_file_path, destination_path, target_path)
file_name = dir.get_next()
if error:
push_error("plugin error - copying path: %s" % error)
else:
push_error("plugin error - accessing path: %s" % dir_path)
func _complete(target_path : String) -> void:
completed.emit(target_path)
close()
func _wait_for_scan_and_complete(target_path : String) -> void:
var timer: Timer = Timer.new()
var callable := func():
if EditorInterface.get_resource_filesystem().is_scanning(): return
timer.stop()
_complete(target_path)
timer.queue_free()
timer.timeout.connect(callable)
add_child(timer)
timer.start(RUNNING_CHECK_DELAY)
func _delayed_saving_and_next_prompt(target_path : String) -> void:
var timer: Timer = Timer.new()
var callable := func():
timer.stop()
EditorInterface.save_all_scenes()
EditorInterface.get_resource_filesystem().scan()
_wait_for_scan_and_complete(target_path)
timer.queue_free()
timer.timeout.connect(callable)
add_child(timer)
timer.start(RESAVING_DELAY)
func _copy_to_directory(target_path : String) -> void:
if not target_path.ends_with("/"):
target_path += "/"
_copy_directory_path(relative_path, target_path)
_delayed_saving_and_next_prompt(target_path)
func _on_destination_dialog_dir_selected(dir):
_copy_to_directory(dir)
func _on_destination_dialog_canceled():
canceled.emit()
close()