@tool class_name MaaacksGameTemplatePlugin extends EditorPlugin const PLUGIN_PATH = "res://addons/maaacks_game_template/" const PLUGIN_NAME = "Maaack's Minimal Game Template" const PROJECT_SETTINGS_PATH = "maaacks_game_template/" const APIClient = preload(PLUGIN_PATH + "utilities/api_client.gd") const DownloadAndExtract = preload(PLUGIN_PATH + "utilities/download_and_extract.gd") const CopyAndEdit = preload(PLUGIN_PATH + "installer/copy_and_edit_files.gd") const EXAMPLES_RELATIVE_PATH = "examples/" const MAIN_SCENE_RELATIVE_PATH = "scenes/opening/opening.tscn" const OVERRIDE_RELATIVE_PATH = "installer/override.cfg" const THEMES_DIRECTORY_RELATIVE_PATH = "resources/themes" const WINDOW_OPEN_DELAY : float = 0.5 const RUNNING_CHECK_DELAY : float = 0.25 const OPEN_EDITOR_DELAY : float = 0.1 const MAX_PHYSICS_FRAMES_FROM_START : int = 60 const AVAILABLE_TRANSLATIONS : Array = ["en", "fr"] static var instance : MaaacksGameTemplatePlugin var selected_theme : String var update_plugin_tool_string : String static func get_plugin_name() -> String: return PLUGIN_NAME static func get_settings_path() -> String: return PROJECT_SETTINGS_PATH static func get_plugin_path() -> String: return PLUGIN_PATH static func get_plugin_examples_path() -> String: return get_plugin_path() + EXAMPLES_RELATIVE_PATH static func get_copy_path() -> String: var copy_path = ProjectSettings.get_setting(PROJECT_SETTINGS_PATH + "copy_path", get_plugin_examples_path()) if not copy_path.ends_with("/"): copy_path += "/" return copy_path func _on_theme_selected(theme_resource_path: String) -> void: selected_theme = theme_resource_path func _update_gui_theme() -> void: if selected_theme.is_empty(): return ProjectSettings.set_setting("gui/theme/custom", selected_theme) ProjectSettings.save() func _on_visibility_changed_to_hidden(dialog_window : Window) -> void: if dialog_window and dialog_window.is_inside_tree() and not dialog_window.visible: dialog_window.queue_free() func open_theme_selection_dialog(target_path : String) -> void: selected_theme = "" var theme_selection_scene : PackedScene = load(get_plugin_path() + "installer/theme_selection_dialog.tscn") var theme_selection_instance : ConfirmationDialog = theme_selection_scene.instantiate() theme_selection_instance.confirmed.connect(_update_gui_theme) theme_selection_instance.theme_selected.connect(_on_theme_selected) theme_selection_instance.visibility_changed.connect(_on_visibility_changed_to_hidden.bind(theme_selection_instance)) add_child(theme_selection_instance) var theme_directores : Array[String] theme_directores.append(target_path + THEMES_DIRECTORY_RELATIVE_PATH) theme_selection_instance.theme_directories = theme_directores func open_setup_complete_dialog(_target_path : String) -> void: var setup_complete_scene : PackedScene = load(get_plugin_path() + "installer/setup_complete_dialog.tscn") var setup_complete_instance : AcceptDialog = setup_complete_scene.instantiate() setup_complete_instance.visibility_changed.connect(_on_visibility_changed_to_hidden.bind(setup_complete_instance)) add_child(setup_complete_instance) func _delayed_open_setup_complete_dialog(target_path : String) -> void: var timer: Timer = Timer.new() var callable := func(): timer.stop() open_setup_complete_dialog(target_path) timer.queue_free() timer.timeout.connect(callable) add_child(timer) timer.start(WINDOW_OPEN_DELAY) func _update_main_scene(target_path : String, main_scene_path : String) -> void: ProjectSettings.set_setting("application/run/main_scene", main_scene_path) ProjectSettings.save() _delayed_open_setup_complete_dialog(target_path) func is_main_scene_set(target_path : String = get_copy_path()) -> bool: var current_main_scene_path = ProjectSettings.get_setting("application/run/main_scene", "") var new_main_scene_path = target_path + MAIN_SCENE_RELATIVE_PATH return current_main_scene_path == new_main_scene_path func _check_main_scene_needs_updating(target_path : String) -> void: if not is_main_scene_set(target_path): open_main_scene_confirmation_dialog(target_path) return _delayed_open_setup_complete_dialog(target_path) func open_main_scene_confirmation_dialog(target_path : String) -> void: var main_confirmation_scene : PackedScene = load(get_plugin_path() + "installer/main_scene_confirmation_dialog.tscn") var main_confirmation_instance : ConfirmationDialog = main_confirmation_scene.instantiate() var new_main_scene_path = target_path + MAIN_SCENE_RELATIVE_PATH if main_confirmation_instance.has_method(&"set_main_scene_text"): main_confirmation_instance.set_main_scene_text(new_main_scene_path) main_confirmation_instance.confirmed.connect(_update_main_scene.bind(target_path, new_main_scene_path)) main_confirmation_instance.canceled.connect(_delayed_open_setup_complete_dialog.bind(target_path)) main_confirmation_instance.visibility_changed.connect(_on_visibility_changed_to_hidden.bind(main_confirmation_instance)) add_child(main_confirmation_instance) func _open_play_opening_confirmation_dialog(target_path : String) -> void: var play_confirmation_scene : PackedScene = load(get_plugin_path() + "installer/play_opening_confirmation_dialog.tscn") var play_confirmation_instance : ConfirmationDialog = play_confirmation_scene.instantiate() play_confirmation_instance.confirmed.connect(_run_opening_scene.bind(target_path)) play_confirmation_instance.canceled.connect(_check_main_scene_needs_updating.bind(target_path)) play_confirmation_instance.visibility_changed.connect(_on_visibility_changed_to_hidden.bind(play_confirmation_instance)) add_child(play_confirmation_instance) func _open_delete_examples_confirmation_dialog(target_path : String) -> void: var delete_confirmation_scene : PackedScene = load(get_plugin_path() + "installer/delete_examples_confirmation_dialog.tscn") var delete_confirmation_instance : ConfirmationDialog = delete_confirmation_scene.instantiate() delete_confirmation_instance.confirmed.connect(_delete_source_examples_directory.bind(target_path)) delete_confirmation_instance.canceled.connect(_check_main_scene_needs_updating.bind(target_path)) delete_confirmation_instance.visibility_changed.connect(_on_visibility_changed_to_hidden.bind(delete_confirmation_instance)) add_child(delete_confirmation_instance) func open_delete_examples_short_confirmation_dialog() -> void: var delete_confirmation_scene : PackedScene = load(get_plugin_path() + "installer/delete_examples_short_confirmation_dialog.tscn") var delete_confirmation_instance : ConfirmationDialog = delete_confirmation_scene.instantiate() delete_confirmation_instance.confirmed.connect(_delete_source_examples_directory) delete_confirmation_instance.visibility_changed.connect(_on_visibility_changed_to_hidden.bind(delete_confirmation_instance)) add_child(delete_confirmation_instance) func _run_opening_scene(target_path : String) -> void: var opening_scene_path = target_path + MAIN_SCENE_RELATIVE_PATH EditorInterface.play_custom_scene(opening_scene_path) var timer: Timer = Timer.new() var callable := func() -> void: if EditorInterface.is_playing_scene(): return timer.stop() _open_delete_examples_confirmation_dialog(target_path) timer.queue_free() timer.timeout.connect(callable) add_child(timer) timer.start(RUNNING_CHECK_DELAY) func _delete_directory_recursive(dir_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 relative_path = dir_path.trim_prefix(get_plugin_examples_path()) var full_file_path = dir_path + file_name if dir.current_is_dir(): _delete_directory_recursive(full_file_path) else: error = dir.remove(file_name) file_name = dir.get_next() if error: push_error("plugin error - deleting path: %s" % error) else: push_error("plugin error - accessing path: %s" % dir) dir.remove(dir_path) func _delete_source_examples_directory(target_path : String = "") -> void: var examples_path = get_plugin_examples_path() var dir := DirAccess.open("res://") if dir.dir_exists(examples_path): _delete_directory_recursive(examples_path) EditorInterface.get_resource_filesystem().scan() if not target_path.is_empty(): _check_main_scene_needs_updating(target_path) 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_override_file() -> void: var override_path : String = get_plugin_path() + OVERRIDE_RELATIVE_PATH _raw_copy_file_path(override_path, "res://"+override_path.get_file()) func _add_translations() -> void: var dir := DirAccess.open("res://") var translations : PackedStringArray = ProjectSettings.get_setting("internationalization/locale/translations", []) for available_translation in AVAILABLE_TRANSLATIONS: var translation_path = get_plugin_path() + ("base/translations/menus_translations.%s.translation" % available_translation) if dir.file_exists(translation_path) and translation_path not in translations: translations.append(translation_path) ProjectSettings.set_setting("internationalization/locale/translations", translations) func _on_completed_copy_to_directory(target_path : String) -> void: ProjectSettings.set_setting(PROJECT_SETTINGS_PATH + "copy_path", target_path) ProjectSettings.save() _copy_override_file() _open_play_opening_confirmation_dialog(target_path) func open_input_icons_dialog() -> void: var input_icons_scene : PackedScene = load(get_plugin_path() + "installer/kenney_input_prompts_installer.tscn") var input_icons_instance = input_icons_scene.instantiate() input_icons_instance.copy_dir_path = get_copy_path() add_child(input_icons_instance) func open_copy_and_edit_dialog() -> void: var copy_and_edit_scene : PackedScene = load(get_plugin_path() + "installer/copy_and_edit_files.tscn") var copy_and_edit_instance : CopyAndEdit = copy_and_edit_scene.instantiate() copy_and_edit_instance.completed.connect(_on_completed_copy_to_directory) copy_and_edit_instance.canceled.connect(_check_main_scene_needs_updating.bind(get_copy_path())) add_child(copy_and_edit_instance) func _open_confirmation_dialog() -> void: var confirmation_scene : PackedScene = load(get_plugin_path() + "installer/copy_confirmation_dialog.tscn") var confirmation_instance : ConfirmationDialog = confirmation_scene.instantiate() confirmation_instance.confirmed.connect(open_copy_and_edit_dialog) confirmation_instance.canceled.connect(_check_main_scene_needs_updating.bind(get_copy_path())) confirmation_instance.visibility_changed.connect(_on_visibility_changed_to_hidden.bind(confirmation_instance)) add_child(confirmation_instance) func _open_check_plugin_version() -> void: if ProjectSettings.has_setting(PROJECT_SETTINGS_PATH + "disable_update_check"): if ProjectSettings.get_setting(PROJECT_SETTINGS_PATH + "disable_update_check"): return else: ProjectSettings.set_setting(PROJECT_SETTINGS_PATH + "disable_update_check", false) ProjectSettings.save() var check_version_scene : PackedScene = load(get_plugin_path() + "installer/check_plugin_version.tscn") var check_version_instance : Node = check_version_scene.instantiate() check_version_instance.auto_start = true check_version_instance.new_version_detected.connect(_add_update_plugin_tool_option) add_child(check_version_instance) func open_update_plugin() -> void: var update_plugin_scene : PackedScene = load(get_plugin_path() + "installer/update_plugin.tscn") var update_plugin_instance : Node = update_plugin_scene.instantiate() update_plugin_instance.auto_start = true update_plugin_instance.update_completed.connect(_remove_update_plugin_tool_option) add_child(update_plugin_instance) func open_setup_wizard() -> void: var setup_wizard_scene : PackedScene = load(get_plugin_path() + "installer/setup_wizard.tscn") var setup_wizard_instance : Node = setup_wizard_scene.instantiate() add_child(setup_wizard_instance) func _add_update_plugin_tool_option(new_version : String) -> void: update_plugin_tool_string = "Update %s to v%s..." % [get_plugin_name(), new_version] add_tool_menu_item(update_plugin_tool_string, open_update_plugin) func _remove_update_plugin_tool_option() -> void: if update_plugin_tool_string.is_empty(): return remove_tool_menu_item(update_plugin_tool_string) update_plugin_tool_string = "" func _show_plugin_dialogues() -> void: if ProjectSettings.has_setting(PROJECT_SETTINGS_PATH + "disable_install_wizard") : if ProjectSettings.get_setting(PROJECT_SETTINGS_PATH + "disable_install_wizard") : return _open_confirmation_dialog() ProjectSettings.set_setting(PROJECT_SETTINGS_PATH + "disable_install_wizard", true) ProjectSettings.save() func _resave_if_recently_opened() -> void: if Engine.get_physics_frames() < MAX_PHYSICS_FRAMES_FROM_START: var timer: Timer = Timer.new() var callable := func(): if Engine.get_frames_per_second() >= 10: timer.stop() EditorInterface.save_scene() timer.queue_free() timer.timeout.connect(callable) add_child(timer) timer.start(OPEN_EDITOR_DELAY) func _add_audio_bus(bus_name : String) -> void: var has_bus_name := false for bus_idx in range(AudioServer.bus_count): var existing_bus_name := AudioServer.get_bus_name(bus_idx) if existing_bus_name == bus_name: has_bus_name = true break if not has_bus_name: AudioServer.add_bus() var new_bus_idx := AudioServer.bus_count - 1 AudioServer.set_bus_name(new_bus_idx, bus_name) AudioServer.set_bus_send(new_bus_idx, &"Master") ProjectSettings.save() func _install_audio_busses() -> void: if ProjectSettings.has_setting(PROJECT_SETTINGS_PATH + "disable_install_audio_busses"): if ProjectSettings.get_setting(PROJECT_SETTINGS_PATH + "disable_install_audio_busses") : return _add_audio_bus("Music") _add_audio_bus("SFX") ProjectSettings.set_setting(PROJECT_SETTINGS_PATH + "disable_install_audio_busses", true) ProjectSettings.save() func _add_tool_options() -> void: add_tool_menu_item("Run " + get_plugin_name() + " Setup...", open_setup_wizard) _open_check_plugin_version() func _remove_tool_options() -> void: remove_tool_menu_item("Run " + get_plugin_name() + " Setup...") _remove_update_plugin_tool_option() func _enter_tree() -> void: _install_audio_busses() _add_tool_options() _add_translations() _show_plugin_dialogues() _resave_if_recently_opened() instance = self func _exit_tree() -> void: _remove_tool_options() instance = null