commit 7eb0dea424488e6ed42f2e25c7427f47b8069da2 Author: Eric Vande Voort Date: Tue Jan 7 16:10:03 2025 -0600 init diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..8ad74f7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4709183 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Godot 4+ specific ignores +.godot/ diff --git a/addons/AsepriteWizard/aseprite/aseprite.gd b/addons/AsepriteWizard/aseprite/aseprite.gd new file mode 100644 index 0000000..733a1f8 --- /dev/null +++ b/addons/AsepriteWizard/aseprite/aseprite.gd @@ -0,0 +1,280 @@ +@tool +extends RefCounted + +var _config = preload("../config/config.gd").new() + +# +# Output: +# { +# "data_file": file path to the json file +# "sprite_sheet": file path to the raw image file +# } +func export_file(file_name: String, output_folder: String, options: Dictionary) -> Dictionary: + var exception_pattern = options.get('exception_pattern', "") + var only_visible_layers = options.get('only_visible_layers', false) + var output_name = file_name if options.get('output_filename') == "" else options.get('output_filename', file_name) + var first_frame_only = options.get("first_frame_only", false) + var basename = _get_file_basename(output_name) + var output_dir = ProjectSettings.globalize_path(output_folder) + var data_file = "%s/%s.json" % [output_dir, basename] + var sprite_sheet = "%s/%s.png" % [output_dir, basename] + var output = [] + var arguments = _export_command_common_arguments(file_name, data_file, sprite_sheet) + + if not only_visible_layers: + arguments.push_front("--all-layers") + + if first_frame_only: + arguments.push_front("'[0, 0]'") + arguments.push_front("--frame-range") + + _add_sheet_type_arguments(arguments, options) + + _add_ignore_layer_arguments(file_name, arguments, exception_pattern) + + var local_sprite_sheet_path = ProjectSettings.localize_path(sprite_sheet) + var is_new = not ResourceLoader.exists(local_sprite_sheet_path) + + var exit_code = _execute(arguments, output) + if exit_code != 0: + printerr('aseprite: failed to export spritesheet') + printerr(output) + return {} + + return { + "data_file": ProjectSettings.localize_path(data_file), + "sprite_sheet": local_sprite_sheet_path, + "is_first_import": is_new, + } + + +func export_layers(file_name: String, output_folder: String, options: Dictionary) -> Array: + var exception_pattern = options.get('exception_pattern', "") + var only_visible_layers = options.get('only_visible_layers', false) + var basename = _get_file_basename(file_name) + var layers = list_layers(file_name, only_visible_layers) + var exception_regex = _compile_regex(exception_pattern) + + var output = [] + + for layer in layers: + if layer != "" and (not exception_regex or exception_regex.search(layer) == null): + output.push_back(export_layer(file_name, layer, output_folder, options)) + + return output + + +func export_layer(file_name: String, layer_name: String, output_folder: String, options: Dictionary) -> Dictionary: + var output_prefix = options.get('output_filename', "").strip_edges() + var output_dir = output_folder.replace("res://", "./").strip_edges() + var data_file = "%s/%s%s.json" % [output_dir, output_prefix, layer_name] + var sprite_sheet = "%s/%s%s.png" % [output_dir, output_prefix, layer_name] + var first_frame_only = options.get("first_frame_only", false) + var output = [] + var arguments = _export_command_common_arguments(file_name, data_file, sprite_sheet) + arguments.push_front(layer_name) + arguments.push_front("--layer") + + if first_frame_only: + arguments.push_front("'[0, 0]'") + arguments.push_front("--frame-range") + + _add_sheet_type_arguments(arguments, options) + + var local_sprite_sheet_path = ProjectSettings.localize_path(sprite_sheet) + var is_new = not ResourceLoader.exists(local_sprite_sheet_path) + + var exit_code = _execute(arguments, output) + if exit_code != 0: + print('aseprite: failed to export layer spritesheet') + print(output) + return {} + + return { + "data_file": ProjectSettings.localize_path(data_file), + "sprite_sheet": local_sprite_sheet_path, + "is_first_import": is_new, + } + + +func _add_ignore_layer_arguments(file_name: String, arguments: Array, exception_pattern: String): + var layers = _get_exception_layers(file_name, exception_pattern) + if not layers.is_empty(): + for l in layers: + arguments.push_front(l) + arguments.push_front('--ignore-layer') + +func _add_sheet_type_arguments(arguments: Array, options : Dictionary): + var column_count : int = options.get("column_count", 0) + if column_count > 0: + arguments.push_back("--merge-duplicates") # Yes, this is undocumented + arguments.push_back("--sheet-columns") + arguments.push_back(column_count) + else: + arguments.push_back("--sheet-pack") + + +func _get_exception_layers(file_name: String, exception_pattern: String) -> Array: + var layers = list_layers(file_name) + var regex = _compile_regex(exception_pattern) + if regex == null: + return [] + + var exception_layers = [] + for layer in layers: + if regex.search(layer) != null: + exception_layers.push_back(layer) + + return exception_layers + + +func list_layers(file_name: String, only_visible = false) -> Array: + var output = [] + var arguments = ["-b", "--list-layers", file_name] + + if not only_visible: + arguments.push_front("--all-layers") + + var exit_code = _execute(arguments, output) + + if exit_code != 0: + printerr('aseprite: failed listing layers') + printerr(output) + return [] + + if output.is_empty(): + return output + + var raw = output[0].split('\n') + var sanitized = [] + for s in raw: + sanitized.append(s.strip_edges()) + return sanitized + + +func list_slices(file_name: String) -> Array: + var output = [] + var arguments = ["-b", "--list-slices", file_name] + + var exit_code = _execute(arguments, output) + + if exit_code != 0: + printerr('aseprite: failed listing slices') + printerr(output) + return [] + + if output.is_empty(): + return output + + var raw = output[0].split('\n') + var sanitized = [] + for s in raw: + sanitized.append(s.strip_edges()) + return sanitized + + +func _export_command_common_arguments(source_name: String, data_path: String, spritesheet_path: String) -> Array: + return [ + "-b", + "--list-tags", + "--list-slices", + "--data", + data_path, + "--format", + "json-array", + "--sheet", + spritesheet_path, + source_name + ] + + +func _execute(arguments, output): + return OS.execute(_aseprite_command(), arguments, output, true, true) + + +func _aseprite_command() -> String: + return _config.is_command_or_control_pressed() + + +func _get_file_basename(file_path: String) -> String: + return file_path.get_file().trim_suffix('.%s' % file_path.get_extension()) + + +func _compile_regex(pattern): + if pattern == "": + return + + var rgx = RegEx.new() + if rgx.compile(pattern) == OK: + return rgx + + printerr('exception regex error') + + +func test_command(): + var exit_code = OS.execute(_aseprite_command(), ['--version'], [], true) + return exit_code == 0 + + +func is_valid_spritesheet(content): + return content.has("frames") and content.has("meta") and content.meta.has('image') + + +func get_content_frames(content): + return content.frames if typeof(content.frames) == TYPE_ARRAY else content.frames.values() + + +func get_slice_rect(content: Dictionary, slice_name: String) -> Variant: + if not content.has("meta") or not content.meta.has("slices"): + return null + for slice in content.meta.slices: + if slice.name == slice_name: + if slice.keys.size() > 0: + var p = slice.keys[0].bounds + return Rect2(p.x, p.y, p.w, p.h) + return null + + +## +## Exports tileset layers +## +## Return (dictionary): +## data_file: path to aseprite generated JSON file +## sprite_sheet: localized path to spritesheet file +func export_tileset_texture(file_name: String, output_folder: String, options: Dictionary) -> Dictionary: + var exception_pattern = options.get('exception_pattern', "") + var only_visible_layers = options.get('only_visible_layers', false) + var output_name = file_name if options.get('output_filename') == "" else options.get('output_filename', file_name) + var basename = _get_file_basename(output_name) + var output_dir = ProjectSettings.globalize_path(output_folder) + var data_path = "%s/%s.json" % [output_dir, basename] + var sprite_sheet = "%s/%s.png" % [output_dir, basename] + var output = [] + + var arguments = [ + "-b", + "--export-tileset", + "--data", + data_path, + "--format", + "json-array", + "--sheet", + sprite_sheet, + file_name + ] + + if not only_visible_layers: + arguments.push_front("--all-layers") + + _add_ignore_layer_arguments(file_name, arguments, exception_pattern) + + var exit_code = _execute(arguments, output) + if exit_code != 0: + printerr('aseprite: failed to export spritesheet') + printerr(output) + return {} + + return { + "data_file": ProjectSettings.localize_path(data_path), + "sprite_sheet": ProjectSettings.localize_path(sprite_sheet) + } diff --git a/addons/AsepriteWizard/aseprite/file_exporter.gd b/addons/AsepriteWizard/aseprite/file_exporter.gd new file mode 100644 index 0000000..f88425c --- /dev/null +++ b/addons/AsepriteWizard/aseprite/file_exporter.gd @@ -0,0 +1,143 @@ +@tool +extends RefCounted + +var result_code = preload("../config/result_codes.gd") +var _aseprite = preload("aseprite.gd").new() + +enum { + FILE_EXPORT_MODE, + LAYERS_EXPORT_MODE +} + +## +## Generate Aseprite spritesheet and data files for source. +## +## Options: +## output_folder (string) +## output_filename (string, optional) +## export_mode (FILE_EXPORT_MODE, LAYERS_EXPORT_MODE) default: FILE_EXPORT_MODE +## exception_pattern (string, optional) +## only_visible_layers (boolean, optional) +## +## Return: +## Array +## Dictionary +## sprite_sheet: sprite sheet path +## data_file: json file path +## +func generate_aseprite_files(source_file: String, options: Dictionary): + var check = _initial_checks(source_file, options) + + if check != result_code.SUCCESS: + return result_code.error(check) + + match options.get('export_mode', FILE_EXPORT_MODE): + FILE_EXPORT_MODE: + var output = _aseprite.export_file(source_file, options.output_folder, options) + if output.is_empty(): + return result_code.error(result_code.ERR_ASEPRITE_EXPORT_FAILED) + return result_code.result([output]) + LAYERS_EXPORT_MODE: + var output = _aseprite.export_layers(source_file, options.output_folder, options) + if output.is_empty(): + return result_code.error(result_code.ERR_NO_VALID_LAYERS_FOUND) + return result_code.result(output) + _: + return result_code.error(result_code.ERR_UNKNOWN_EXPORT_MODE) + + +## +## Generate Aseprite spritesheet and data file for source. +## +## Options: +## output_folder (string) +## output_filename (string, optional) +## layer (string, optional) +## exception_pattern (string, optional) +## only_visible_layers (boolean, optional) +## +## Return: +## Dictionary +## sprite_sheet: sprite sheet path +## data_file: json file path +## +func generate_aseprite_file(source_file: String, options: Dictionary) -> Dictionary: + var check = _initial_checks(source_file, options) + + if check != result_code.SUCCESS: + return result_code.error(check) + + var output + + if options.get("layer", "") == "": + output = _aseprite.export_file(source_file, options.output_folder, options) + else: + output = _aseprite.export_layer(source_file, options.layer, options.output_folder, options) + + if output.is_empty(): + return result_code.error(result_code.ERR_ASEPRITE_EXPORT_FAILED) + + return result_code.result(output) + + +## +## Generate a spritesheet with all tilesets in the file +## +## Options: +## exception_pattern (string) +## only_visible_layers (boolean) +## output_filename (string) +## output_folder (string) +## +## Return: +## Dictionary +## sprite_sheet: sprite sheet path +## data_file: json file path +## +func generate_tileset_files(source_file: String, options = {}) -> Dictionary: + var check = _initial_checks(source_file, options) + + if check != result_code.SUCCESS: + return result_code.error(check) + + var output = _aseprite.export_tileset_texture(source_file, options.output_folder, options) + + if output.is_empty(): + return result_code.error(result_code.ERR_ASEPRITE_EXPORT_FAILED) + + return result_code.result(output) + + +## +## Perform initial source file and output folder checks +## +func _initial_checks(source: String, options: Dictionary) -> int: + if not _aseprite.test_command(): + return result_code.ERR_ASEPRITE_CMD_NOT_FOUND + + if not FileAccess.file_exists(source): + return result_code.ERR_SOURCE_FILE_NOT_FOUND + + if not DirAccess.dir_exists_absolute(options.output_folder): + return result_code.ERR_OUTPUT_FOLDER_NOT_FOUND + + return result_code.SUCCESS + + +## +## Load Aseprite source data file and fails if the +## content is not valid +## +func load_json_content(source_file: String) -> Dictionary: + var file = FileAccess.open(source_file, FileAccess.READ) + if file == null: + return result_code.error(FileAccess.get_open_error()) + var test_json_conv = JSON.new() + test_json_conv.parse(file.get_as_text()) + + var content = test_json_conv.get_data() + + if not _aseprite.is_valid_spritesheet(content): + return result_code.error(result_code.ERR_INVALID_ASEPRITE_SPRITESHEET) + + return result_code.result(content) diff --git a/addons/AsepriteWizard/config/config.gd b/addons/AsepriteWizard/config/config.gd new file mode 100644 index 0000000..3ddd619 --- /dev/null +++ b/addons/AsepriteWizard/config/config.gd @@ -0,0 +1,261 @@ +@tool +extends RefCounted + +# GLOBAL SETTINGS +const _CONFIG_SECTION_KEY = 'aseprite' +const _COMMAND_KEY = 'aseprite/general/command_path' + +# PROJECT SETTINGS + +# animation import defaults +const _DEFAULT_EXCLUSION_PATTERN_KEY = 'aseprite/animation/layers/exclusion_pattern' +const _DEFAULT_ONLY_VISIBLE_LAYERS = 'aseprite/animation/layers/only_include_visible_layers_by_default' +const _DEFAULT_LOOP_EX_PREFIX = '_' +const _LOOP_ENABLED = 'aseprite/animation/loop/enabled' +const _LOOP_EXCEPTION_PREFIX = 'aseprite/animation/loop/exception_prefix' +const _USE_METADATA = 'aseprite/animation/storage/use_metadata' + +# cleanup +const _REMOVE_SOURCE_FILES_KEY = 'aseprite/import/cleanup/remove_json_file' +const _SET_VISIBLE_TRACK_AUTOMATICALLY = 'aseprite/import/cleanup/automatically_hide_sprites_not_in_animation' + +# automatic importer +const _IMPORTER_ENABLE_KEY = 'aseprite/import/import_plugin/enable_automatic_importer' +const _DEFAULT_IMPORTER_KEY = 'aseprite/import/import_plugin/default_automatic_importer' + +const IMPORTER_SPRITEFRAMES_NAME = "SpriteFrames" +const IMPORTER_NOOP_NAME = "No Import" +const IMPORTER_TILESET_TEXTURE_NAME = "Tileset Texture" +const IMPORTER_STATIC_TEXTURE_NAME = "Static Texture" + +# wizard history +const _WIZARD_HISTORY = "wizard_history" +const _HISTORY_MAX_ENTRIES = 'aseprite/wizard/history/max_history_entries' +const _HISTORY_DEFAULT_MAX_ENTRIES = 100 + +## DEPRECATED (v7.4.0): remove in a next major version +const _HISTORY_CONFIG_FILE_CFG_KEY = 'aseprite/wizard/history/cache_file_path' +## DEPRECATED (v7.4.0): remove in a next major version +const _DEFAULT_HISTORY_CONFIG_FILE_PATH = 'res://.aseprite_wizard_history' + + +# SpriteFrames import last config +const _STANDALONE_SPRITEFRAMES_LAST_IMPORT_CFG = "standalone_sf_last_import_cfg" + +# export +const _EXPORTER_ENABLE_KEY = 'aseprite/animation/storage/enable_metadata_removal_on_export' + +var _editor_settings: EditorSettings = EditorInterface.get_editor_settings() + +####################################################### +# GLOBAL CONFIGS +###################################################### + +func default_command() -> String: + match OS.get_name(): + "Windows": + return "C:\\\\Steam\\steamapps\\common\\Aseprite\\aseprite.exe" + "macOS": + return "/Applications/Aseprite.app/Contents/MacOS/aseprite" + _: + return 'aseprite' + + +func is_command_or_control_pressed() -> String: + var command = _editor_settings.get(_COMMAND_KEY) if _editor_settings.has_setting(_COMMAND_KEY) else "" + return command if command != "" else default_command() + + +####################################################### +# PROJECT SETTINGS +###################################################### + +# remove this config in the next major version +func is_importer_enabled() -> bool: + return _get_project_setting(_IMPORTER_ENABLE_KEY, false) + + +func get_default_importer() -> String: + return _get_project_setting(_DEFAULT_IMPORTER_KEY, IMPORTER_SPRITEFRAMES_NAME if is_importer_enabled() else IMPORTER_NOOP_NAME) + + +func is_exporter_enabled() -> bool: + return _get_project_setting(_EXPORTER_ENABLE_KEY, true) + + +func should_remove_source_files() -> bool: + return _get_project_setting(_REMOVE_SOURCE_FILES_KEY, true) + + +func is_default_animation_loop_enabled() -> bool: + return _get_project_setting(_LOOP_ENABLED, true) + + +func get_animation_loop_exception_prefix() -> String: + return _get_project_setting(_LOOP_EXCEPTION_PREFIX, _DEFAULT_LOOP_EX_PREFIX) + +func is_use_metadata_enabled() -> bool: + return _get_project_setting(_USE_METADATA, true) + + +func get_default_exclusion_pattern() -> String: + return _get_project_setting(_DEFAULT_EXCLUSION_PATTERN_KEY, "") + + +func should_include_only_visible_layers_by_default() -> bool: + return _get_project_setting(_DEFAULT_ONLY_VISIBLE_LAYERS, false) + + +func get_history_max_entries() -> int: + return _get_project_setting(_HISTORY_MAX_ENTRIES, _HISTORY_DEFAULT_MAX_ENTRIES) + + +func get_import_history() -> Array: + return get_plugin_metadata(_WIZARD_HISTORY, []) + + +func get_old_import_history() -> Array: + var history = [] + var history_path := _get_history_file_path() + + if not FileAccess.file_exists(history_path): + return history + + var file_object = FileAccess.open(history_path, FileAccess.READ) + + while not file_object.eof_reached(): + var line = file_object.get_line() + if line: + var test_json_conv = JSON.new() + test_json_conv.parse(line) + history.push_back(test_json_conv.get_data()) + + return history + + +func is_set_visible_track_automatically_enabled() -> bool: + return _get_project_setting(_SET_VISIBLE_TRACK_AUTOMATICALLY, false) + + +func save_import_history(history: Array): + set_plugin_metadata(_WIZARD_HISTORY, history) + + +## DEPRECATED +func _get_history_file_path() -> String: + return _get_project_setting(_HISTORY_CONFIG_FILE_CFG_KEY, _DEFAULT_HISTORY_CONFIG_FILE_PATH) + + +## used for old history migration. Should be removed together with the history cleanup +func has_old_history() -> bool: + return ProjectSettings.has_setting(_HISTORY_CONFIG_FILE_CFG_KEY) or FileAccess.file_exists(_DEFAULT_HISTORY_CONFIG_FILE_PATH) + +## used for old history migration. Should be removed together with the history cleanup +func remove_old_history_setting() -> void: + DirAccess.remove_absolute(_get_history_file_path()) + if ProjectSettings.has_setting(_HISTORY_CONFIG_FILE_CFG_KEY): + ProjectSettings.clear(_HISTORY_CONFIG_FILE_CFG_KEY) + +#========================================================= +# IMPORT CONFIGS +#========================================================= +## Return config for last import done via standalone SpriteFrames import dock +func get_standalone_spriteframes_last_import_config() -> Dictionary: + return get_plugin_metadata(_STANDALONE_SPRITEFRAMES_LAST_IMPORT_CFG, {}) + +## Set config for last import done via standalone SpriteFrames import dock +func set_standalone_spriteframes_last_import_config(data: Dictionary) -> void: + set_plugin_metadata(_STANDALONE_SPRITEFRAMES_LAST_IMPORT_CFG, data) + + +func clear_standalone_spriteframes_last_import_config() -> void: + set_plugin_metadata(_STANDALONE_SPRITEFRAMES_LAST_IMPORT_CFG, {}) + + +func get_plugin_metadata(key: String, default: Variant = null) -> Variant: + return _editor_settings.get_project_metadata(_CONFIG_SECTION_KEY, key, default) + + +func set_plugin_metadata(key: String, data: Variant): + _editor_settings.set_project_metadata(_CONFIG_SECTION_KEY, key, data) + + +####################################################### +# INITIALIZATION +###################################################### +func initialize_project_settings(): + _initialize_project_cfg(_DEFAULT_EXCLUSION_PATTERN_KEY, "", TYPE_STRING) + _initialize_project_cfg(_DEFAULT_ONLY_VISIBLE_LAYERS, false, TYPE_BOOL) + _initialize_project_cfg(_LOOP_ENABLED, true, TYPE_BOOL) + _initialize_project_cfg(_LOOP_EXCEPTION_PREFIX, _DEFAULT_LOOP_EX_PREFIX, TYPE_STRING) + _initialize_project_cfg(_USE_METADATA, true, TYPE_BOOL) + + _initialize_project_cfg(_REMOVE_SOURCE_FILES_KEY, true, TYPE_BOOL) + _initialize_project_cfg( + _DEFAULT_IMPORTER_KEY, + IMPORTER_SPRITEFRAMES_NAME if is_importer_enabled() else IMPORTER_NOOP_NAME, + TYPE_STRING, + PROPERTY_HINT_ENUM, + "%s,%s,%s,%s" % [IMPORTER_NOOP_NAME, IMPORTER_SPRITEFRAMES_NAME, IMPORTER_TILESET_TEXTURE_NAME, IMPORTER_STATIC_TEXTURE_NAME] + ) + + _initialize_project_cfg(_EXPORTER_ENABLE_KEY, true, TYPE_BOOL) + + # TODO remove (history max entries) + #_initialize_project_cfg(_HISTORY_CONFIG_FILE_CFG_KEY, _DEFAULT_HISTORY_CONFIG_FILE_PATH, TYPE_STRING, PROPERTY_HINT_GLOBAL_FILE) + _initialize_project_cfg(_HISTORY_MAX_ENTRIES, _HISTORY_DEFAULT_MAX_ENTRIES, TYPE_INT) + + _initialize_project_cfg(_SET_VISIBLE_TRACK_AUTOMATICALLY, false, TYPE_BOOL) + + ProjectSettings.save() + + _initialize_editor_cfg(_COMMAND_KEY, default_command(), TYPE_STRING) + + +func clear_project_settings(): + var _all_settings = [ + _DEFAULT_EXCLUSION_PATTERN_KEY, + _LOOP_ENABLED, + _LOOP_EXCEPTION_PREFIX, + _USE_METADATA, + _REMOVE_SOURCE_FILES_KEY, + _DEFAULT_IMPORTER_KEY, + _EXPORTER_ENABLE_KEY, + _HISTORY_MAX_ENTRIES, + _SET_VISIBLE_TRACK_AUTOMATICALLY, + _DEFAULT_ONLY_VISIBLE_LAYERS, + ] + for key in _all_settings: + ProjectSettings.clear(key) + ProjectSettings.save() + + +func _initialize_project_cfg(key: String, default_value, type: int, hint: int = PROPERTY_HINT_NONE, hint_string = null): + if not ProjectSettings.has_setting(key): + ProjectSettings.set(key, default_value) + ProjectSettings.set_initial_value(key, default_value) + ProjectSettings.add_property_info({ + "name": key, + "type": type, + "hint": hint, + "hint_string": hint_string, + }) + + +func _get_project_setting(key: String, default_value): + if not ProjectSettings.has_setting(key): + return default_value + + var p = ProjectSettings.get(key) + return p if p != null else default_value + + +func _initialize_editor_cfg(key: String, default_value, type: int, hint: int = PROPERTY_HINT_NONE): + if not _editor_settings.has_setting(key): + _editor_settings.set(key, default_value) + _editor_settings.set_initial_value(key, default_value, false) + _editor_settings.add_property_info({ + "name": key, + "type": type, + "hint": hint, + }) diff --git a/addons/AsepriteWizard/config/config_dialog.gd b/addons/AsepriteWizard/config/config_dialog.gd new file mode 100644 index 0000000..50431ec --- /dev/null +++ b/addons/AsepriteWizard/config/config_dialog.gd @@ -0,0 +1,29 @@ +@tool +extends PopupPanel + +var _config = preload("./config.gd").new() + +@onready var _aseprite_command_field = $MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer/aseprite_command +@onready var _version_label = $MarginContainer/VBoxContainer/VBoxContainer/version_found + +func _ready(): + _aseprite_command_field.text = _config.is_command_or_control_pressed() + _version_label.modulate.a = 0 + + +func _on_close_button_up(): + self.hide() + + +func _on_test_pressed(): + var output = [] + if _test_command(output): + _version_label.text = "%s found." % "\n".join(PackedStringArray(output)).strip_edges() + else: + _version_label.text = "Command not found." + _version_label.modulate.a = 1 + + +func _test_command(output): + var exit_code = OS.execute(_aseprite_command_field.text, ['--version'], output, true, true) + return exit_code == 0 diff --git a/addons/AsepriteWizard/config/config_dialog.tscn b/addons/AsepriteWizard/config/config_dialog.tscn new file mode 100644 index 0000000..56c92f0 --- /dev/null +++ b/addons/AsepriteWizard/config/config_dialog.tscn @@ -0,0 +1,80 @@ +[gd_scene load_steps=2 format=3 uid="uid://d0whlywijwa6s"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/config/config_dialog.gd" id="1"] + +[node name="config_dialog" type="PopupPanel"] +title = "Aseprite Wizard Config" +size = Vector2i(624, 236) +visible = true +unresizable = false +borderless = false +min_size = Vector2i(624, 236) +content_scale_mode = 1 +script = ExtResource("1") + +[node name="MarginContainer" type="MarginContainer" parent="."] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 4.0 +offset_top = 4.0 +offset_right = -532.0 +offset_bottom = -416.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +theme_override_constants/margin_left = 10 +theme_override_constants/margin_top = 10 +theme_override_constants/margin_right = 10 +theme_override_constants/margin_bottom = 10 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 +text = "This configuration moved. +- To edit the aseprite command path, go to Editor > Editor Settings > Aseprite. +- To edit project specific settings, go to Project > Project Settings > Aseprite." + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="Aseprite Command" type="Label" parent="MarginContainer/VBoxContainer/VBoxContainer"] +layout_mode = 2 +tooltip_text = "Define the path for Aseprite command" +mouse_filter = 1 +text = "Aseprite Command Path" + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="aseprite_command" type="LineEdit" parent="MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +editable = false +caret_blink = true + +[node name="test" type="Button" parent="MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +text = "Test" + +[node name="version_found" type="Label" parent="MarginContainer/VBoxContainer/VBoxContainer"] +modulate = Color(1, 1, 1, 0) +layout_mode = 2 +size_flags_horizontal = 3 +text = "Aseprite version found" + +[node name="VBoxContainer2" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 +alignment = 2 + +[node name="close" type="Button" parent="MarginContainer/VBoxContainer/VBoxContainer2"] +layout_mode = 2 +text = "Close" + +[connection signal="pressed" from="MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer/test" to="." method="_on_test_pressed"] +[connection signal="button_up" from="MarginContainer/VBoxContainer/VBoxContainer2/close" to="." method="_on_close_button_up"] diff --git a/addons/AsepriteWizard/config/result_codes.gd b/addons/AsepriteWizard/config/result_codes.gd new file mode 100644 index 0000000..25baff8 --- /dev/null +++ b/addons/AsepriteWizard/config/result_codes.gd @@ -0,0 +1,37 @@ +@tool +extends RefCounted + +const SUCCESS = 0 +const ERR_ASEPRITE_CMD_NOT_FOUND = 1 +const ERR_SOURCE_FILE_NOT_FOUND = 2 +const ERR_OUTPUT_FOLDER_NOT_FOUND = 3 +const ERR_ASEPRITE_EXPORT_FAILED = 4 +const ERR_UNKNOWN_EXPORT_MODE = 5 +const ERR_NO_VALID_LAYERS_FOUND = 6 +const ERR_INVALID_ASEPRITE_SPRITESHEET = 7 + + +static func get_error_message(code: int): + match code: + ERR_ASEPRITE_CMD_NOT_FOUND: + return "Aseprite command failed. Please, check if the right command is in your PATH or configured through \"Editor > Editor Settings > Aseprite > General > Command Path\"." + ERR_SOURCE_FILE_NOT_FOUND: + return "source file does not exist" + ERR_OUTPUT_FOLDER_NOT_FOUND: + return "output location does not exist" + ERR_ASEPRITE_EXPORT_FAILED: + return "unable to import file" + ERR_INVALID_ASEPRITE_SPRITESHEET: + return "aseprite generated bad data file" + ERR_NO_VALID_LAYERS_FOUND: + return "no valid layers found" + _: + return "import failed with code %d" % code + + +static func error(error_code: int): + return { "code": error_code, "content": null, "is_ok": false } + + +static func result(result): + return { "code": SUCCESS, "content": result, "is_ok": true } diff --git a/addons/AsepriteWizard/config/wizard_config.gd b/addons/AsepriteWizard/config/wizard_config.gd new file mode 100644 index 0000000..a6a3469 --- /dev/null +++ b/addons/AsepriteWizard/config/wizard_config.gd @@ -0,0 +1,85 @@ +@tool +extends RefCounted + +const WIZARD_CONFIG_META_NAME = "_aseprite_wizard_config_" +const WIZARD_CONFIG_MARKER = "aseprite_wizard_config" +const WIZARD_INTERFACE_CONFIG_META_NAME = "_aseprite_wizard_interface_config_" +const SOURCE_FILE_HASH_META_NAME = "_aseprite_wizard_source_file_hash_" +const SEPARATOR = "|=" + +static func encode(object: Dictionary): + var text = "%s\n" % WIZARD_CONFIG_MARKER + + for prop in object: + text += "%s%s%s\n" % [prop, SEPARATOR, object[prop]] + + return Marshalls.utf8_to_base64(text) + + +static func decode(string: String): + var decoded = _decode_base64(string) + if not _is_wizard_config(decoded): + return null + + var cfg = decoded.split("\n") + var config = {} + for c in cfg: + var parts = c.split(SEPARATOR, 1) + if parts.size() == 2: + var key = parts[0].strip_edges() + var value = parts[1].strip_edges() + + #Convert bool properties + if key == "only_visible" or key == "op_exp": + match value: + "True": + config[key] = true + "False": + config[key] = false + _: + config[key] = false + else: + config[key] = value + + return config + + +static func _decode_base64(string: String): + if string != "": + return Marshalls.base64_to_utf8(string) + return null + + +static func _is_wizard_config(cfg) -> bool: + return cfg != null and cfg.begins_with(WIZARD_CONFIG_MARKER) + + +static func load_config(node: Object): + if node.has_meta(WIZARD_CONFIG_META_NAME): + return node.get_meta(WIZARD_CONFIG_META_NAME) + + return decode(node.editor_description) + + +static func save_config(node: Object, cfg: Dictionary): + node.set_meta(WIZARD_CONFIG_META_NAME, cfg) + + +static func load_interface_config(node: Node, default: Dictionary = {}) -> Dictionary: + if node.has_meta(WIZARD_INTERFACE_CONFIG_META_NAME): + return node.get_meta(WIZARD_INTERFACE_CONFIG_META_NAME) + return default + + +static func save_interface_config(node: Node, cfg:Dictionary) -> void: + node.set_meta(WIZARD_INTERFACE_CONFIG_META_NAME, cfg) + + +static func set_source_hash(node: Object, hash: String) -> void: + node.set_meta(SOURCE_FILE_HASH_META_NAME, hash) + + +static func get_source_hash(node: Object) -> String: + if node.has_meta(SOURCE_FILE_HASH_META_NAME): + return node.get_meta(SOURCE_FILE_HASH_META_NAME) + return "" diff --git a/addons/AsepriteWizard/creators/animation_player/animation_creator.gd b/addons/AsepriteWizard/creators/animation_player/animation_creator.gd new file mode 100644 index 0000000..0a782f3 --- /dev/null +++ b/addons/AsepriteWizard/creators/animation_player/animation_creator.gd @@ -0,0 +1,306 @@ +@tool +extends "../base_sprite_resource_creator.gd" + +var _DEFAULT_ANIMATION_LIBRARY = "" # GLOBAL + +func create_animations(target_node: Node, player: AnimationPlayer, aseprite_files: Dictionary, options: Dictionary): + var result = _import(target_node, player, aseprite_files, options) + + if result != result_code.SUCCESS: + printerr(result_code.get_error_message(result)) + + +func _import(target_node: Node, player: AnimationPlayer, aseprite_files: Dictionary, options: Dictionary): + var source_file = aseprite_files.data_file + var sprite_sheet = aseprite_files.sprite_sheet + var data = _aseprite_file_exporter.load_json_content(source_file) + + if not data.is_ok: + return data.code + + var content = data.content + + var context = {} + + if target_node is CanvasItem: + target_node.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST + else: + target_node.texture_filter = BaseMaterial3D.TEXTURE_FILTER_NEAREST + + _setup_texture(target_node, sprite_sheet, content, context, options.slice != "") + var result = _configure_animations(target_node, player, content, context, options) + if result != result_code.SUCCESS: + return result + + return _cleanup_animations(target_node, player, content, options) + + +func _load_texture(sprite_sheet: String) -> Texture2D: + var texture = ResourceLoader.load(sprite_sheet, 'Image', ResourceLoader.CACHE_MODE_IGNORE) + texture.take_over_path(sprite_sheet) + return texture + + +func _configure_animations(target_node: Node, player: AnimationPlayer, content: Dictionary, context: Dictionary, options: Dictionary): + var frames = _aseprite.get_content_frames(content) + var slice_rect = null + if options.slice != "": + options["slice_rect"] = _aseprite.get_slice_rect(content, options.slice) + + if not player.has_animation_library(_DEFAULT_ANIMATION_LIBRARY): + player.add_animation_library(_DEFAULT_ANIMATION_LIBRARY, AnimationLibrary.new()) + + if content.meta.has("frameTags") and content.meta.frameTags.size() > 0: + var result = result_code.SUCCESS + for tag in content.meta.frameTags: + var selected_frames = frames.slice(tag.from, tag.to + 1) + result = _add_animation_frames(target_node, player, tag.name, selected_frames, context, options, tag.direction, int(tag.get("repeat", -1))) + if result != result_code.SUCCESS: + break + return result + else: + return _add_animation_frames(target_node, player, "default", frames, context, options) + + +func _add_animation_frames(target_node: Node, player: AnimationPlayer, anim_name: String, frames: Array, context: Dictionary, options: Dictionary, direction = 'forward', repeat = -1): + var animation_name = anim_name + var library_name = _DEFAULT_ANIMATION_LIBRARY + var is_loopable = _config.is_default_animation_loop_enabled() + var slice_rect = options.get("slice_rect") + var is_importing_slice: bool = slice_rect != null + + var anim_tokens := anim_name.split("/") + + if anim_tokens.size() > 2: + push_error("Invalid animation name: %s" % animation_name) + return + elif anim_tokens.size() == 2: + library_name = anim_tokens[0] + animation_name = anim_tokens[1] + + if not _validate_animation_name(animation_name): + push_error("Invalid animation name: %s" % animation_name) + return + + # Create library if doesn't exist + if library_name != _DEFAULT_ANIMATION_LIBRARY and not player.has_animation_library(library_name): + player.add_animation_library(library_name, AnimationLibrary.new()) + + # Check loop + if animation_name.begins_with(_config.get_animation_loop_exception_prefix()): + animation_name = animation_name.substr(_config.get_animation_loop_exception_prefix().length()) + is_loopable = not is_loopable + + # Add library + if not player.get_animation_library(library_name).has_animation(animation_name): + player.get_animation_library(library_name).add_animation(animation_name, Animation.new()) + + var full_name = ( + animation_name if library_name == "" else "%s/%s" % [library_name, animation_name] + ) + + var animation = player.get_animation(full_name) + _cleanup_tracks(target_node, player, animation) + _create_meta_tracks(target_node, player, animation) + + var frame_track = _get_property_track_path(player, target_node, _get_frame_property(is_importing_slice)) + var frame_track_index = _create_track(target_node, animation, frame_track) + + if direction == "reverse" or direction == "pingpong_reverse": + frames.reverse() + + var animation_length = 0 + + var repetition = 1 + + if repeat != -1: + is_loopable = false + repetition = repeat + + for i in range(repetition): + for frame in frames: + var frame_key = _get_frame_key(target_node, frame, context, slice_rect) + animation.track_insert_key(frame_track_index, animation_length, frame_key) + animation_length += frame.duration / 1000 + + # Godot 4 has an Animation.LOOP_PINGPONG mode, however it does not + # behave like in Aseprite, so I'm keeping the custom implementation + if direction.begins_with("pingpong"): + var working_frames = frames.duplicate() + working_frames.remove_at(working_frames.size() - 1) + if is_loopable or (repetition > 1 and i < repetition - 1): + working_frames.remove_at(0) + working_frames.reverse() + + for frame in working_frames: + var frame_key = _get_frame_key(target_node, frame, context, slice_rect) + animation.track_insert_key(frame_track_index, animation_length, frame_key) + animation_length += frame.duration / 1000 + + # if keep_anim_length is enabled only adjust length if + # - there aren't other tracks besides metas and frame + # - the current animation is shorter than new one + if not options.keep_anim_length or (animation.get_track_count() == (_get_meta_prop_names().size() + 1) or animation.length < animation_length): + animation.length = animation_length + + animation.loop_mode = Animation.LOOP_LINEAR if is_loopable else Animation.LOOP_NONE + + return result_code.SUCCESS + + +const _INVALID_TOKENS := ["/", ":", ",", "["] + + +func _validate_animation_name(name: String) -> bool: + return not _INVALID_TOKENS.any(func(token: String): return token in name) + + +func _create_track(target_node: Node, animation: Animation, track: String): + var track_index = animation.find_track(track, Animation.TYPE_VALUE) + + if track_index != -1: + animation.remove_track(track_index) + + track_index = animation.add_track(Animation.TYPE_VALUE) + animation.track_set_path(track_index, track) + animation.track_set_interpolation_loop_wrap(track_index, false) + animation.value_track_set_update_mode(track_index, Animation.UPDATE_DISCRETE) + + return track_index + + +func _get_property_track_path(player: AnimationPlayer, target_node: Node, prop: String) -> String: + var node_path = player.get_node(player.root_node).get_path_to(target_node) + return "%s:%s" % [node_path, prop] + + +func _cleanup_animations(target_node: Node, player: AnimationPlayer, content: Dictionary, options: Dictionary): + if not (content.meta.has("frameTags") and content.meta.frameTags.size() > 0): + return result_code.SUCCESS + + _remove_unused_animations(content, player) + + if options.get("cleanup_hide_unused_nodes", false): + _hide_unused_nodes(target_node, player, content) + + return result_code.SUCCESS + + +func _remove_unused_animations(content: Dictionary, player: AnimationPlayer): + pass # FIXME it's not removing unused animations anymore. Sample impl bellow +# var tags = ["RESET"] +# for t in content.meta.frameTags: +# var a = t.name +# if a.begins_with(_config.get_animation_loop_exception_prefix()): +# a = a.substr(_config.get_animation_loop_exception_prefix().length()) +# tags.push_back(a) + +# var track = _get_frame_track_path(player, sprite) +# for a in player.get_animation_list(): +# if tags.has(a): +# continue +# +# var animation = player.get_animation(a) +# if animation.get_track_count() != 1: +# var t = animation.find_track(track) +# if t != -1: +# animation.remove_track(t) +# continue +# +# if animation.find_track(track) != -1: +# player.remove_animation(a) + + +func _hide_unused_nodes(target_node: Node, player: AnimationPlayer, content: Dictionary): + var root_node := player.get_node(player.root_node) + var all_animations := player.get_animation_list() + var all_sprite_nodes := [] + var animation_sprites := {} + + for a in all_animations: + var animation := player.get_animation(a) + var sprite_nodes := [] + + for track_idx in animation.get_track_count(): + var raw_path := animation.track_get_path(track_idx) + + if raw_path.get_subname(0) == "visible": + continue + + var path := _remove_properties_from_path(raw_path) + var sprite_node := root_node.get_node(path) + + if !(sprite_node is Sprite2D || sprite_node is Sprite3D): + continue + + if sprite_nodes.has(sprite_node): + continue + sprite_nodes.append(sprite_node) + + animation_sprites[animation] = sprite_nodes + for sn in sprite_nodes: + if all_sprite_nodes.has(sn): + continue + all_sprite_nodes.append(sn) + + for animation in animation_sprites: + var sprite_nodes : Array = animation_sprites[animation] + for node in all_sprite_nodes: + if sprite_nodes.has(node): + continue + var visible_track = _get_property_track_path(player, node, "visible") + if animation.find_track(visible_track, Animation.TYPE_VALUE) != -1: + continue + var visible_track_index = _create_track(node, animation, visible_track) + animation.track_insert_key(visible_track_index, 0, false) + + +func list_layers(file: String, only_visibles = false) -> Array: + return _aseprite.list_layers(file, only_visibles) + + +func list_slices(file: String) -> Array: + return _aseprite.list_slices(file) + + +func _remove_properties_from_path(path: NodePath) -> NodePath: + var string_path := path as String + if !(":" in string_path): + return string_path as NodePath + + var property_path := path.get_concatenated_subnames() as String + string_path = string_path.substr(0, string_path.length() - property_path.length() - 1) + + return string_path as NodePath + + +func _create_meta_tracks(target_node: Node, player: AnimationPlayer, animation: Animation): + for prop in _get_meta_prop_names(): + var track = _get_property_track_path(player, target_node, prop) + var track_index = _create_track(target_node, animation, track) + animation.track_insert_key(track_index, 0, true if prop == "visible" else target_node.get(prop)) + + +func _cleanup_tracks(target_node: Node, player: AnimationPlayer, animation: Animation): + for track_key in ["texture", "hframes", "vframes", "region_rect", "frame"]: + var track = _get_property_track_path(player, target_node, track_key) + var track_index = animation.find_track(track, Animation.TYPE_VALUE) + if track_index != -1: + animation.remove_track(track_index) + + +func _setup_texture(target_node: Node, sprite_sheet: String, content: Dictionary, context: Dictionary, is_importing_slice: bool): + push_error("_setup_texture not implemented!") + + +func _get_frame_property(is_importing_slice: bool) -> String: + push_error("_get_frame_property not implemented!") + return "" + + +func _get_frame_key(target_node: Node, frame: Dictionary, context: Dictionary, slice_info: Variant): + push_error("_get_frame_key not implemented!") + + +func _get_meta_prop_names(): + push_error("_get_meta_prop_names not implemented!") diff --git a/addons/AsepriteWizard/creators/animation_player/sprite_animation_creator.gd b/addons/AsepriteWizard/creators/animation_player/sprite_animation_creator.gd new file mode 100644 index 0000000..93a9a6d --- /dev/null +++ b/addons/AsepriteWizard/creators/animation_player/sprite_animation_creator.gd @@ -0,0 +1,48 @@ +extends "animation_creator.gd" + +func _get_meta_prop_names(): + return [ "visible" ] + + +func _setup_texture(sprite: Node, sprite_sheet: String, content: Dictionary, context: Dictionary, is_importing_slice: bool): + var texture = _load_texture(sprite_sheet) + sprite.texture = texture + + if content.frames.is_empty(): + return + + if is_importing_slice: + sprite.region_enabled = true + sprite.hframes = 1 + sprite.vframes = 1 + sprite.frame = 0 + else: + sprite.region_enabled = false + sprite.hframes = content.meta.size.w / content.frames[0].sourceSize.w + sprite.vframes = content.meta.size.h / content.frames[0].sourceSize.h + + +func _get_frame_property(is_importing_slice: bool) -> String: + return "frame" if not is_importing_slice else "region_rect" + + +func _get_frame_key(sprite: Node, frame: Dictionary, context: Dictionary, slice_info: Variant): + if slice_info != null: + return _create_slice_rect(frame, slice_info) + return _calculate_frame_index(sprite,frame) + + +func _calculate_frame_index(sprite: Node, frame: Dictionary) -> int: + var column = floor(frame.frame.x * sprite.hframes / sprite.texture.get_width()) + var row = floor(frame.frame.y * sprite.vframes / sprite.texture.get_height()) + return (row * sprite.hframes) + column + + +func _create_slice_rect(frame_data: Dictionary, slice_rect: Rect2) -> Rect2: + var frame = frame_data.frame + return Rect2( + frame.x + slice_rect.position.x, + frame.y + slice_rect.position.y, + slice_rect.size.x, + slice_rect.size.y + ) diff --git a/addons/AsepriteWizard/creators/animation_player/texture_rect_animation_creator.gd b/addons/AsepriteWizard/creators/animation_player/texture_rect_animation_creator.gd new file mode 100644 index 0000000..7cc80df --- /dev/null +++ b/addons/AsepriteWizard/creators/animation_player/texture_rect_animation_creator.gd @@ -0,0 +1,32 @@ +extends "animation_creator.gd" + + +func _get_meta_prop_names(): + return [ "visible" ] + + +func _setup_texture(target_node: Node, sprite_sheet: String, content: Dictionary, context: Dictionary, _is_importing_slice: bool): + context["base_texture"] = _load_texture(sprite_sheet) + + +func _get_frame_property(_is_importing_slice: bool) -> String: + return "texture" + + +func _get_frame_key(target_node: Node, frame: Dictionary, context: Dictionary, slice_info: Variant): + return _get_atlas_texture(context["base_texture"], frame, slice_info) + + +func _get_atlas_texture(base_texture: Texture2D, frame_data: Dictionary, slice_info: Variant) -> AtlasTexture: + var tex = AtlasTexture.new() + tex.atlas = base_texture + tex.region = Rect2(Vector2(frame_data.frame.x, frame_data.frame.y), Vector2(frame_data.frame.w, frame_data.frame.h)) + tex.filter_clip = true + + if slice_info != null: + tex.region.position.x += slice_info.position.x + tex.region.position.y += slice_info.position.y + tex.region.size.x = slice_info.size.x + tex.region.size.y = slice_info.size.y + + return tex diff --git a/addons/AsepriteWizard/creators/base_sprite_resource_creator.gd b/addons/AsepriteWizard/creators/base_sprite_resource_creator.gd new file mode 100644 index 0000000..257b7ea --- /dev/null +++ b/addons/AsepriteWizard/creators/base_sprite_resource_creator.gd @@ -0,0 +1,8 @@ +@tool +extends RefCounted + +var result_code = preload("../config/result_codes.gd") +var _aseprite = preload("../aseprite/aseprite.gd").new() +var _aseprite_file_exporter = preload("../aseprite/file_exporter.gd").new() + +var _config = preload("../config/config.gd").new() diff --git a/addons/AsepriteWizard/creators/sprite_frames/sprite_frames_creator.gd b/addons/AsepriteWizard/creators/sprite_frames/sprite_frames_creator.gd new file mode 100644 index 0000000..029e6ab --- /dev/null +++ b/addons/AsepriteWizard/creators/sprite_frames/sprite_frames_creator.gd @@ -0,0 +1,234 @@ +@tool +extends "../base_sprite_resource_creator.gd" + +enum { + FILE_EXPORT_MODE, + LAYERS_EXPORT_MODE +} + +### +### Create SpriteFrames from aseprite files and insert +### them to the animated_sprite node +### +func create_animations(animated_sprite: Node, aseprite_files: Dictionary, options: Dictionary) -> void: + var sprite_frames_result = _create_sprite_frames(aseprite_files, options) + if not sprite_frames_result.is_ok: + printerr(result_code.get_error_message(sprite_frames_result.code)) + return + + animated_sprite.frames = sprite_frames_result.content + + if animated_sprite is CanvasItem: + animated_sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST + else: + animated_sprite.texture_filter = BaseMaterial3D.TEXTURE_FILTER_NEAREST + + +func create_resources(source_files: Array, options: Dictionary = {}) -> Dictionary: + var resources = [] + + for o in source_files: + if o.is_empty(): + return result_code.error(result_code.ERR_ASEPRITE_EXPORT_FAILED) + + var resource = _create_sprite_frames(o, options) + + if not resource.is_ok: + return resource + + resources.push_back({ + "data_file": o.data_file, + "resource": resource.content, + }) + + return result_code.result(resources) + + +func _create_sprite_frames(data: Dictionary, options: Dictionary) -> Dictionary: + var aseprite_resources = _load_aseprite_resources(data) + if not aseprite_resources.is_ok: + return aseprite_resources + + return result_code.result( + _create_sprite_frames_with_animations( + aseprite_resources.content.metadata, + aseprite_resources.content.texture, + options, + ) + ) + + +func _load_aseprite_resources(aseprite_data: Dictionary): + var content_result = _aseprite_file_exporter.load_json_content(aseprite_data.data_file) + + if not content_result.is_ok: + return content_result + + var texture = _load_texture(aseprite_data.sprite_sheet) + + return result_code.result({ + "metadata": content_result.content, + "texture": texture + }) + + +func save_resources(resources: Array) -> int: + for resource in resources: + var code = _save_resource(resource.resource, resource.data_file) + if code != OK: + return code + return OK + + +func _save_resource(resource, source_path: String) -> int: + var save_path = "%s.%s" % [source_path.get_basename(), "res"] + var code = ResourceSaver.save(resource, save_path, ResourceSaver.FLAG_REPLACE_SUBRESOURCE_PATHS) + resource.take_over_path(save_path) + return code + + +func _create_sprite_frames_with_animations(content: Dictionary, texture, options: Dictionary) -> SpriteFrames: + var frame_cache = {} + var frames = _aseprite.get_content_frames(content) + var sprite_frames := SpriteFrames.new() + sprite_frames.remove_animation("default") + + var frame_rect: Variant = null + + # currently, aseprite does not work with the --slice option, so we need to manually + # do it. https://github.com/aseprite/aseprite/issues/2469 + if options.get("slice", "") != "": + frame_rect = _aseprite.get_slice_rect(content, options.slice) + + if content.meta.has("frameTags") and content.meta.frameTags.size() > 0: + for tag in content.meta.frameTags: + var selected_frames = frames.slice(tag.from, tag.to + 1) + _add_animation_frames(sprite_frames, tag.name, selected_frames, texture, frame_rect, tag.direction, int(tag.get("repeat", -1)), frame_cache) + else: + _add_animation_frames(sprite_frames, "default", frames, texture, frame_rect) + + return sprite_frames + + +func _add_animation_frames( + sprite_frames: SpriteFrames, + anim_name: String, + frames: Array, + texture, + frame_rect: Variant, + direction = 'forward', + repeat = -1, + frame_cache = {} +): + var animation_name := anim_name + var is_loopable = _config.is_default_animation_loop_enabled() + + var loop_prefix = _config.get_animation_loop_exception_prefix() + if animation_name.begins_with(loop_prefix): + animation_name = anim_name.trim_prefix(loop_prefix) + is_loopable = not is_loopable + + sprite_frames.add_animation(animation_name) + + var min_duration = _get_min_duration(frames) + var fps = _calculate_fps(min_duration) + + if direction == "reverse" or direction == "pingpong_reverse": + frames.reverse() + + var repetition = 1 + + if repeat != -1: + is_loopable = false + repetition = repeat + + for i in range(repetition): + for frame in frames: + _add_to_sprite_frames(sprite_frames, animation_name, texture, frame, min_duration, frame_cache, frame_rect) + + if direction.begins_with("pingpong"): + var working_frames = frames.duplicate() + working_frames.remove_at(working_frames.size() - 1) + if is_loopable or (repetition > 1 and i < repetition - 1): + working_frames.remove_at(0) + working_frames.reverse() + + for frame in working_frames: + _add_to_sprite_frames(sprite_frames, animation_name, texture, frame, min_duration, frame_cache, frame_rect) + + sprite_frames.set_animation_loop(animation_name, is_loopable) + sprite_frames.set_animation_speed(animation_name, fps) + + +func _calculate_fps(min_duration: int) -> float: + return ceil(1000.0 / min_duration) + + +func _get_min_duration(frames) -> int: + var min_duration = 100000 + for frame in frames: + if frame.duration < min_duration: + min_duration = frame.duration + return min_duration + + +func _load_texture(path) -> CompressedTexture2D: + return ResourceLoader.load(path, "CompressedTexture2D", ResourceLoader.CACHE_MODE_REPLACE) + + +func _add_to_sprite_frames( + sprite_frames, + animation_name: String, + texture, + frame: Dictionary, + min_duration: int, + frame_cache: Dictionary, + frame_rect: Variant, +): + var atlas : AtlasTexture = _create_atlastexture_from_frame(texture, frame, sprite_frames, frame_cache, frame_rect) + var duration = frame.duration / min_duration + sprite_frames.add_frame(animation_name, atlas, duration) + + +func _create_atlastexture_from_frame( + image, + frame_data, + sprite_frames: SpriteFrames, + frame_cache: Dictionary, + frame_rect: Variant, +) -> AtlasTexture: + var frame = frame_data.frame + var region := Rect2(frame.x, frame.y, frame.w, frame.h) + + # this is to manually set the slice + if frame_rect != null: + region.position.x += frame_rect.position.x + region.position.y += frame_rect.position.y + region.size.x = frame_rect.size.x + region.size.y = frame_rect.size.y + + var key := "%s_%s_%s_%s" % [frame.x, frame.y, frame.w, frame.h] + var texture = frame_cache.get(key) + + if texture != null and texture.atlas == image: + return texture + + var atlas_texture := AtlasTexture.new() + atlas_texture.atlas = image + atlas_texture.region = region + + frame_cache[key] = atlas_texture + + return atlas_texture + + +func list_layers(file: String, only_visibles = false) -> Array: + return _aseprite.list_layers(file, only_visibles) + + +func list_slices(file: String) -> Array: + return _aseprite.list_slices(file) + + +func _get_file_basename(file_path: String) -> String: + return file_path.get_file().trim_suffix('.%s' % file_path.get_extension()) diff --git a/addons/AsepriteWizard/creators/static_texture/texture_creator.gd b/addons/AsepriteWizard/creators/static_texture/texture_creator.gd new file mode 100644 index 0000000..8cebcf1 --- /dev/null +++ b/addons/AsepriteWizard/creators/static_texture/texture_creator.gd @@ -0,0 +1,26 @@ +@tool +extends "../base_sprite_resource_creator.gd" + +func load_texture(target_node: Node, aseprite_files: Dictionary, options: Dictionary) -> void: + var source_file = aseprite_files.data_file + var sprite_sheet = aseprite_files.sprite_sheet + var data = _aseprite_file_exporter.load_json_content(source_file) + var texture = ResourceLoader.load(sprite_sheet) + + if not data.is_ok: + printerr("Failed to load aseprite source %s" % source_file) + return + + if options.slice == "": + target_node.texture = texture + else: + var region = _aseprite.get_slice_rect(data.content, options.slice) + var atlas_texture := AtlasTexture.new() + atlas_texture.atlas = texture + atlas_texture.region = region + target_node.texture = atlas_texture + + if target_node is CanvasItem: + target_node.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST + else: + target_node.texture_filter = BaseMaterial3D.TEXTURE_FILTER_NEAREST diff --git a/addons/AsepriteWizard/export/metadata_export_plugin.gd b/addons/AsepriteWizard/export/metadata_export_plugin.gd new file mode 100644 index 0000000..c982edc --- /dev/null +++ b/addons/AsepriteWizard/export/metadata_export_plugin.gd @@ -0,0 +1,91 @@ +## Some animations might have been imported from outside the project folder, +## potentially leaking the host's path, which is not ideal. +## This plugin removes the aseprite metadata from scenes and spriteframes before +## generating the game export. +extends EditorExportPlugin + +const wizard_config = preload("../config/wizard_config.gd") + +func _get_name(): + return "aseprite_wizard_metadata_export_plugin" + + +func _export_file(path: String, type: String, features: PackedStringArray) -> void: + match type: + "PackedScene": + _cleanup_scene(path, type) + "SpriteFrames": + _cleanup_spriteframes(path, type) + + +func _cleanup_scene(path: String, type: String): + var scene : PackedScene = ResourceLoader.load(path, type, ResourceLoader.CACHE_MODE_IGNORE) + var scene_changed := false + var root_node := scene.instantiate(PackedScene.GEN_EDIT_STATE_INSTANCE) + var nodes := [root_node] + + #remove_at metadata from scene + while not nodes.is_empty(): + var node : Node = nodes.pop_front() + + for child in node.get_children(): + nodes.push_back(child) + + if _remove_meta(node): + scene_changed = true + + #save scene if changed + if scene_changed: + var filtered_scene := PackedScene.new() + if filtered_scene.pack(root_node) != OK: + print("Error updating scene") + return + + var content := _get_scene_content(path, filtered_scene) + + add_file(path, content, true) + + root_node.free() + + +func _remove_meta(node: Object) -> bool: + if node.has_meta(wizard_config.WIZARD_CONFIG_META_NAME): + node.remove_meta(wizard_config.WIZARD_CONFIG_META_NAME) + return true + + return false + + +func _get_scene_content(path:String, scene:PackedScene) -> PackedByteArray: + var tmp_path = OS.get_cache_dir() + "tmp_scene." + path.get_extension() + ResourceSaver.save(scene, tmp_path) + + var tmp_file = FileAccess.open(tmp_path, FileAccess.READ) + var content : PackedByteArray = tmp_file.get_buffer(tmp_file.get_length()) + tmp_file.close() + + if FileAccess.file_exists(tmp_path): + DirAccess.remove_absolute(tmp_path) + + return content + + +func _cleanup_spriteframes(path: String, type: String): + var resource : SpriteFrames = ResourceLoader.load(path, type, ResourceLoader.CACHE_MODE_IGNORE) + if _remove_meta(resource): + var content := _create_temp_resource(path, resource) + add_file(path, content, true) + + +func _create_temp_resource(path: String, resource: SpriteFrames) -> PackedByteArray: + var tmp_path = OS.get_cache_dir() + "tmp_spriteframes_resource." + path.get_extension() + ResourceSaver.save(resource, tmp_path) + + var tmp_file = FileAccess.open(tmp_path, FileAccess.READ) + var content : PackedByteArray = tmp_file.get_buffer(tmp_file.get_length()) + tmp_file.close() + + if FileAccess.file_exists(tmp_path): + DirAccess.remove_absolute(tmp_path) + + return content diff --git a/addons/AsepriteWizard/importers/noop_import_plugin.gd b/addons/AsepriteWizard/importers/noop_import_plugin.gd new file mode 100644 index 0000000..76e5d0b --- /dev/null +++ b/addons/AsepriteWizard/importers/noop_import_plugin.gd @@ -0,0 +1,57 @@ +@tool +extends EditorImportPlugin + +## +## No-op importer to allow files to be seen and +## managed, but without triggering a real import +## + +var config = preload("../config/config.gd").new() + +func _get_importer_name(): + return "aseprite_wizard.plugin.noop" + + +func _get_visible_name(): + return "Aseprite (No Import)" + + +func _get_recognized_extensions(): + return ["aseprite", "ase"] + + +func _get_save_extension(): + return "res" + + +func _get_resource_type(): + return "PackedDataContainer" + + +func _get_preset_count(): + return 1 + + +func _get_preset_name(i): + return "Default" + + +func _get_priority(): + return 2.0 if config.get_default_importer() == config.IMPORTER_NOOP_NAME else 1.0 + + +func _get_import_order(): + return 1 + + +func _get_import_options(_path, _i): + return [] + + +func _get_option_visibility(path, option, options): + return true + + +func _import(source_file, save_path, options, platform_variants, gen_files): + var container = PackedDataContainer.new() + return ResourceSaver.save(container, "%s.%s" % [save_path, _get_save_extension()]) diff --git a/addons/AsepriteWizard/importers/sprite_frames_import_plugin.gd b/addons/AsepriteWizard/importers/sprite_frames_import_plugin.gd new file mode 100644 index 0000000..0ac8208 --- /dev/null +++ b/addons/AsepriteWizard/importers/sprite_frames_import_plugin.gd @@ -0,0 +1,150 @@ +@tool +extends EditorImportPlugin + +const result_codes = preload("../config/result_codes.gd") + +var config = preload("../config/config.gd").new() +var _aseprite_file_exporter = preload("../aseprite/file_exporter.gd").new() +var _sf_creator = preload("../creators/sprite_frames/sprite_frames_creator.gd").new() +var file_system: EditorFileSystem = EditorInterface.get_resource_filesystem() + +func _get_importer_name(): + # ideally this should be called aseprite_wizard.plugin.spriteframes + # but I'm keeping it like this to avoid unnecessary breaking changes + return "aseprite_wizard.plugin" + + +func _get_visible_name(): + return "Aseprite SpriteFrames" + + +func _get_recognized_extensions(): + return ["aseprite", "ase"] + + +func _get_save_extension(): + return "res" + + +func _get_resource_type(): + return "SpriteFrames" + + +func _get_preset_count(): + return 1 + + +func _get_preset_name(i): + return "Default" + + +func _get_priority(): + return 2.0 if config.get_default_importer() == config.IMPORTER_SPRITEFRAMES_NAME else 1.0 + + +func _get_import_order(): + return 1 + + +func _get_import_options(_path, _i): + return [ + {"name": "split_layers", "default_value": false}, + {"name": "exclude_layers_pattern", "default_value": config.get_default_exclusion_pattern()}, + {"name": "only_visible_layers", "default_value": false}, + { + "name": "sheet_type", + "default_value": "Packed", + "property_hint": PROPERTY_HINT_ENUM, + "hint_string": get_sheet_type_hint_string() + }, + ] + + +func _get_option_visibility(path, option, options): + return true + + +static func get_sheet_type_hint_string() -> String: + var hint_string := "Packed" + for number in [2, 4, 8, 16, 32]: + hint_string += ",%s columns" % number + hint_string += ",Strip" + return hint_string + + +func _import(source_file, save_path, options, platform_variants, gen_files): + var absolute_source_file = ProjectSettings.globalize_path(source_file) + var absolute_save_path = ProjectSettings.globalize_path(save_path) + + var source_path = source_file.get_base_dir() + var source_basename = source_file.substr(source_path.length()+1, -1) + source_basename = source_basename.substr(0, source_basename.rfind('.')) + + var export_mode = _sf_creator.LAYERS_EXPORT_MODE if options['split_layers'] else _sf_creator.FILE_EXPORT_MODE + + var aseprite_opts = { + "export_mode": export_mode, + "exception_pattern": options['exclude_layers_pattern'], + "only_visible_layers": options['only_visible_layers'], + "output_filename": '' if export_mode == _sf_creator.FILE_EXPORT_MODE else '%s_' % source_basename, + "column_count" : int(options['sheet_type']) if options['sheet_type'] != "Strip" else 128, + "output_folder": source_path, + } + + var source_files = _aseprite_file_exporter.generate_aseprite_files(absolute_source_file, aseprite_opts) + if not source_files.is_ok: + printerr("ERROR - Could not import aseprite file: %s" % result_codes.get_error_message(source_files.code)) + return FAILED + + + var should_trigger_scan = false + + for sf in source_files.content: + if sf.is_first_import: + file_system.update_file(sf.sprite_sheet) + append_import_external_resource(sf.sprite_sheet) + else: + should_trigger_scan = true + + if should_trigger_scan: + file_system.scan() + + var resources = _sf_creator.create_resources(source_files.content) + + if not resources.is_ok: + printerr("ERROR - Could not import aseprite file: %s" % result_codes.get_error_message(resources.code)) + return FAILED + + if export_mode == _sf_creator.LAYERS_EXPORT_MODE: + # each layer is saved as one resource using base file name to prevent collisions + # the first layer will be saved in the default resource path to prevent + # godot from keeping re-importing it + for resource in resources.content: + var resource_path = "%s.res" % resource.data_file.get_basename(); + var exit_code = ResourceSaver.save(resource.resource, resource_path) + resource.resource.take_over_path(resource_path) + + if exit_code != OK: + printerr("ERROR - Could not persist aseprite file: %s" % result_codes.get_error_message(exit_code)) + return FAILED + + var resource = resources.content[0] + var resource_path = "%s.res" % save_path + var exit_code = ResourceSaver.save(resource.resource, resource_path) + resource.resource.take_over_path(resource_path) + + if config.should_remove_source_files(): + _remove_source_files(source_files.content) + + if exit_code != OK: + printerr("ERROR - Could not persist aseprite file: %s" % result_codes.get_error_message(exit_code)) + return FAILED + + return OK + + +func _remove_source_files(source_files: Array): + for s in source_files: + DirAccess.remove_absolute(s.data_file) + + file_system.call_deferred("scan") diff --git a/addons/AsepriteWizard/importers/static_texture_import_plugin.gd b/addons/AsepriteWizard/importers/static_texture_import_plugin.gd new file mode 100644 index 0000000..4846104 --- /dev/null +++ b/addons/AsepriteWizard/importers/static_texture_import_plugin.gd @@ -0,0 +1,136 @@ +@tool +extends EditorImportPlugin + +## +## Static texture importer. +## Imports first frame from Aseprite file as texture +## + +const result_codes = preload("../config/result_codes.gd") +var _aseprite_file_exporter = preload("../aseprite/file_exporter.gd").new() + +var config = preload("../config/config.gd").new() +var file_system: EditorFileSystem = EditorInterface.get_resource_filesystem() + + +func _get_importer_name(): + return "aseprite_wizard.plugin.static-texture" + + +func _get_visible_name(): + return "Aseprite Texture" + + +func _get_recognized_extensions(): + return ["aseprite", "ase"] + + +func _get_save_extension(): + return "res" + + +func _get_resource_type(): + return "AtlasTexture" + + +func _get_preset_count(): + return 1 + + +func _get_preset_name(i): + return "Default" + + +func _get_priority(): + return 2.0 if config.get_default_importer() == config.IMPORTER_STATIC_TEXTURE_NAME else 0.8 + + +func _get_import_order(): + return 1 + + +func _get_import_options(_path, _i): + return [ + {"name": "exclude_layers_pattern", "default_value": config.get_default_exclusion_pattern()}, + {"name": "only_visible_layers", "default_value": false}, + ] + + +func _get_option_visibility(path, option, options): + return true + + +func _import(source_file, save_path, options, platform_variants, gen_files): + var absolute_source_file = ProjectSettings.globalize_path(source_file) + var absolute_save_path = ProjectSettings.globalize_path(save_path) + var source_path = source_file.get_base_dir() + var source_basename = source_file.substr(source_path.length()+1, -1) + source_basename = source_basename.substr(0, source_basename.rfind('.')) + + var aseprite_opts = { + "exception_pattern": options['exclude_layers_pattern'], + "only_visible_layers": options['only_visible_layers'], + "output_filename": '', + "output_folder": source_path, + "first_frame_only": true, + } + + var result = _generate_texture(absolute_source_file, aseprite_opts) + + if not result.is_ok: + printerr("ERROR - Could not import aseprite file: %s" % result_codes.get_error_message(result.code)) + return FAILED + + var sprite_sheet = result.content.sprite_sheet + var data = result.content.data + + if ResourceLoader.exists(sprite_sheet): + file_system.scan() + else: + file_system.update_file(sprite_sheet) + append_import_external_resource(sprite_sheet) + + var texture: CompressedTexture2D = ResourceLoader.load(sprite_sheet, "CompressedTexture2D", ResourceLoader.CACHE_MODE_REPLACE) + + return _save_resource(texture, save_path, result.content.data_file, data.meta.size) + + +func _generate_texture(absolute_source_file: String, options: Dictionary) -> Dictionary: + var result = _aseprite_file_exporter.generate_aseprite_file(absolute_source_file, options) + + if not result.is_ok: + return result + + var sprite_sheet = result.content.sprite_sheet + + var data_result = _aseprite_file_exporter.load_json_content(result.content.data_file) + + if not data_result.is_ok: + return data_result + + var data = data_result.content + + return result_codes.result({ + "data_file": result.content.data_file, + "sprite_sheet": sprite_sheet, + "data": data + }) + + +func _save_resource(texture: CompressedTexture2D, save_path: String, data_file_path: String, size: Dictionary) -> int: + var resource = AtlasTexture.new() + resource.atlas = texture + resource.region = Rect2(0, 0, size.w, size.h) + + var resource_path = "%s.res" % save_path + var exit_code = ResourceSaver.save(resource, resource_path) + resource.take_over_path(resource_path) + + if config.should_remove_source_files(): + DirAccess.remove_absolute(data_file_path) + file_system.call_deferred("scan") + + if exit_code != OK: + printerr("ERROR - Could not persist aseprite file: %s" % result_codes.get_error_message(exit_code)) + return FAILED + return OK diff --git a/addons/AsepriteWizard/importers/tileset_texture_import_plugin.gd b/addons/AsepriteWizard/importers/tileset_texture_import_plugin.gd new file mode 100644 index 0000000..1a675ac --- /dev/null +++ b/addons/AsepriteWizard/importers/tileset_texture_import_plugin.gd @@ -0,0 +1,135 @@ +@tool +extends EditorImportPlugin + +## +## Tileset texture importer. +## Imports Aseprite tileset layers as an AtlasTexture +## + +const result_codes = preload("../config/result_codes.gd") +var _aseprite_file_exporter = preload("../aseprite/file_exporter.gd").new() + +var config = preload("../config/config.gd").new() +var file_system: EditorFileSystem = EditorInterface.get_resource_filesystem() + + +func _get_importer_name(): + return "aseprite_wizard.plugin.tileset-texture" + + +func _get_visible_name(): + return "Aseprite Tileset Texture" + + +func _get_recognized_extensions(): + return ["aseprite", "ase"] + + +func _get_save_extension(): + return "res" + + +func _get_resource_type(): + return "AtlasTexture" + + +func _get_preset_count(): + return 1 + + +func _get_preset_name(i): + return "Default" + + +func _get_priority(): + return 2.0 if config.get_default_importer() == config.IMPORTER_TILESET_TEXTURE_NAME else 0.9 + + +func _get_import_order(): + return 1 + + +func _get_import_options(_path, _i): + return [ + {"name": "exclude_layers_pattern", "default_value": config.get_default_exclusion_pattern()}, + {"name": "only_visible_layers", "default_value": false}, + ] + + +func _get_option_visibility(path, option, options): + return true + + +func _import(source_file, save_path, options, platform_variants, gen_files): + var absolute_source_file = ProjectSettings.globalize_path(source_file) + var absolute_save_path = ProjectSettings.globalize_path(save_path) + var source_path = source_file.get_base_dir() + var source_basename = source_file.substr(source_path.length()+1, -1) + source_basename = source_basename.substr(0, source_basename.rfind('.')) + + var aseprite_opts = { + "exception_pattern": options['exclude_layers_pattern'], + "only_visible_layers": options['only_visible_layers'], + "output_filename": '', + "output_folder": source_path, + } + + var result = _generate_texture(absolute_source_file, aseprite_opts) + + if not result.is_ok: + printerr("ERROR - Could not import aseprite file: %s" % result_codes.get_error_message(result.code)) + return FAILED + + var sprite_sheet = result.content.sprite_sheet + var data = result.content.data + + if ResourceLoader.exists(sprite_sheet): + file_system.scan() + else: + file_system.update_file(sprite_sheet) + append_import_external_resource(sprite_sheet) + + var texture: CompressedTexture2D = ResourceLoader.load(sprite_sheet, "CompressedTexture2D", ResourceLoader.CACHE_MODE_REPLACE) + + return _save_resource(texture, save_path, result.content.data_file, data.meta.size) + + +func _generate_texture(absolute_source_file: String, options: Dictionary) -> Dictionary: + var result = _aseprite_file_exporter.generate_tileset_files(absolute_source_file, options) + + if not result.is_ok: + return result + + var sprite_sheet = result.content.sprite_sheet + + var data_result = _aseprite_file_exporter.load_json_content(result.content.data_file) + + if not data_result.is_ok: + return data_result + + var data = data_result.content + + return result_codes.result({ + "data_file": result.content.data_file, + "sprite_sheet": sprite_sheet, + "data": data + }) + + +func _save_resource(texture: CompressedTexture2D, save_path: String, data_file_path: String, size: Dictionary) -> int: + var resource = AtlasTexture.new() + resource.atlas = texture + resource.region = Rect2(0, 0, size.w, size.h) + + var resource_path = "%s.res" % save_path + var exit_code = ResourceSaver.save(resource, resource_path) + resource.take_over_path(resource_path) + + if config.should_remove_source_files(): + DirAccess.remove_absolute(data_file_path) + file_system.call_deferred("scan") + + if exit_code != OK: + printerr("ERROR - Could not persist aseprite file: %s" % result_codes.get_error_message(exit_code)) + return FAILED + return OK diff --git a/addons/AsepriteWizard/interface/docks/animated_sprite/animated_sprite_inspector_dock.gd b/addons/AsepriteWizard/interface/docks/animated_sprite/animated_sprite_inspector_dock.gd new file mode 100644 index 0000000..3e83a58 --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/animated_sprite/animated_sprite_inspector_dock.gd @@ -0,0 +1,44 @@ +@tool +extends "../base_inspector_dock.gd" + +var sprite_frames_creator = preload("../../../creators/sprite_frames/sprite_frames_creator.gd").new() + + +func _get_available_layers(global_source_path: String) -> Array: + return sprite_frames_creator.list_layers(global_source_path) + + +func _get_available_slices(global_source_path: String) -> Array: + return sprite_frames_creator.list_slices(global_source_path) + + +func _do_import(): + var root = get_tree().get_edited_scene_root() + + var source_path = ProjectSettings.globalize_path(_source) + var options = { + "output_folder": _output_folder if _output_folder != "" else root.scene_file_path.get_base_dir(), + "exception_pattern": _ex_pattern_field.text, + "only_visible_layers": _visible_layers_field.button_pressed, + "output_filename": _out_filename_field.text, + "layer": _layer, + } + + _save_config() + + var aseprite_output = _aseprite_file_exporter.generate_aseprite_file(source_path, options) + + if not aseprite_output.is_ok: + var error = result_code.get_error_message(aseprite_output.code) + printerr(error) + _show_message(error) + return + + file_system.scan() + await file_system.filesystem_changed + + sprite_frames_creator.create_animations(target_node, aseprite_output.content, { "slice": _slice }) + + wizard_config.set_source_hash(target_node, FileAccess.get_md5(source_path)) + + _handle_cleanup(aseprite_output.content) diff --git a/addons/AsepriteWizard/interface/docks/animated_sprite/animated_sprite_inspector_dock.tscn b/addons/AsepriteWizard/interface/docks/animated_sprite/animated_sprite_inspector_dock.tscn new file mode 100644 index 0000000..e9bf35a --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/animated_sprite/animated_sprite_inspector_dock.tscn @@ -0,0 +1,12 @@ +[gd_scene load_steps=3 format=3 uid="uid://vej7yqkbtd5f"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/animated_sprite/animated_sprite_inspector_dock.gd" id="1"] +[ext_resource type="PackedScene" uid="uid://uxm7b02wry10" path="res://addons/AsepriteWizard/interface/docks/dock_fields.tscn" id="2_2ilip"] + +[node name="animated_sprite_inspector_dock" type="PanelContainer"] +offset_right = 14.0 +offset_bottom = 14.0 +script = ExtResource("1") + +[node name="dock_fields" parent="." instance=ExtResource("2_2ilip")] +layout_mode = 2 diff --git a/addons/AsepriteWizard/interface/docks/animated_sprite/inspector_plugin.gd b/addons/AsepriteWizard/interface/docks/animated_sprite/inspector_plugin.gd new file mode 100644 index 0000000..774a1cd --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/animated_sprite/inspector_plugin.gd @@ -0,0 +1,13 @@ +@tool +extends EditorInspectorPlugin + +const ASInspectorDock = preload("./animated_sprite_inspector_dock.tscn") + +func _can_handle(object): + return object is AnimatedSprite2D || object is AnimatedSprite3D + + +func _parse_end(object): + var dock = ASInspectorDock.instantiate() + dock.target_node = object + add_custom_control(dock) diff --git a/addons/AsepriteWizard/interface/docks/base_inspector_dock.gd b/addons/AsepriteWizard/interface/docks/base_inspector_dock.gd new file mode 100644 index 0000000..8a01e38 --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/base_inspector_dock.gd @@ -0,0 +1,429 @@ +@tool +extends PanelContainer + +const wizard_config = preload("../../config/wizard_config.gd") +const result_code = preload("../../config/result_codes.gd") +var _aseprite_file_exporter = preload("../../aseprite/file_exporter.gd").new() +var config = preload("../../config/config.gd").new() + +var scene: Node +var target_node: Node +var file_system: EditorFileSystem = EditorInterface.get_resource_filesystem() + +var _layer: String = "" +var _slice: String = "" +var _source: String = "" +var _file_dialog_aseprite: EditorFileDialog +var _output_folder_dialog: EditorFileDialog +var _importing := false +var _output_folder := "" +var _out_folder_default := "[Same as scene]" +var _layer_default := "[all]" + +var _interface_section_state + +@onready var _section_title := $dock_fields/VBoxContainer/title as Button + +# general +@onready var _source_field := $dock_fields/VBoxContainer/source/button as Button +# layers +@onready var _layer_section_header := $dock_fields/VBoxContainer/extra/sections/layers/section_header as Button +@onready var _layer_section_container := $dock_fields/VBoxContainer/extra/sections/layers/section_content as MarginContainer +@onready var _layer_field := $dock_fields/VBoxContainer/extra/sections/layers/section_content/content/layer/options as OptionButton +@onready var _visible_layers_field := $dock_fields/VBoxContainer/extra/sections/layers/section_content/content/visible_layers/CheckBox as CheckBox +@onready var _ex_pattern_field := $dock_fields/VBoxContainer/extra/sections/layers/section_content/content/ex_pattern/LineEdit as LineEdit +# slice +@onready var _slice_section_header := $dock_fields/VBoxContainer/extra/sections/slices/section_header as Button +@onready var _slice_section_container := $dock_fields/VBoxContainer/extra/sections/slices/section_content as MarginContainer +@onready var _slice_field := $dock_fields/VBoxContainer/extra/sections/slices/section_content/content/slice/options as OptionButton +# output +@onready var _output_section_header := $dock_fields/VBoxContainer/extra/sections/output/section_header as Button +@onready var _output_section_container := $dock_fields/VBoxContainer/extra/sections/output/section_content as MarginContainer +@onready var _out_folder_field := $dock_fields/VBoxContainer/extra/sections/output/section_content/content/out_folder/button as Button +@onready var _out_filename_field := $dock_fields/VBoxContainer/extra/sections/output/section_content/content/out_filename/LineEdit as LineEdit + +@onready var _import_button := $dock_fields/VBoxContainer/import as Button + +const INTERFACE_SECTION_KEY_LAYER = "layer_section" +const INTERFACE_SECTION_KEY_SLICE = "slice_section" +const INTERFACE_SECTION_KEY_OUTPUT = "output_section" + +@onready var _expandable_sections = { + INTERFACE_SECTION_KEY_LAYER: { "header": _layer_section_header, "content": _layer_section_container}, + INTERFACE_SECTION_KEY_SLICE: { "header": _slice_section_header, "content": _slice_section_container}, + INTERFACE_SECTION_KEY_OUTPUT: { "header": _output_section_header, "content": _output_section_container}, +} + +func _ready(): + _pre_setup() + _setup_interface() + _setup_config() + _setup_field_listeners() + _setup() + _check_for_changes() + + +func _check_for_changes(): + if not _source or _source == "": + return + + var saved_hash = wizard_config.get_source_hash(target_node) + + if saved_hash == "": + return + + if saved_hash != FileAccess.get_md5(_source): + $dock_fields.show_source_change_warning() + + +func _setup_interface(): + _hide_fields() + _show_specific_fields() + var cfg = wizard_config.load_interface_config(target_node) + _interface_section_state = cfg + + _section_title.add_theme_stylebox_override("normal", _section_title.get_theme_stylebox("hover")) + + for key in _expandable_sections: + _adjust_section_visibility(key) + + +func _setup_config(): + var cfg = wizard_config.load_config(target_node) + if cfg == null: + _load_common_default_config() + else: + _load_common_config(cfg) + + +func _load_common_config(cfg): + if cfg.has("source"): + _set_source(cfg.source) + + if cfg.get("layer", "") != "": + _layer_field.clear() + _set_layer(cfg.layer) + + if cfg.get("slice", "") != "": + _slice_field.clear() + _set_slice(cfg.slice) + + _set_out_folder(cfg.get("o_folder", "")) + _out_filename_field.text = cfg.get("o_name", "") + _visible_layers_field.button_pressed = cfg.get("only_visible", false) + _ex_pattern_field.text = cfg.get("o_ex_p", "") + + _load_config(cfg) + + +func _load_common_default_config(): + _ex_pattern_field.text = config.get_default_exclusion_pattern() + _visible_layers_field.button_pressed = config.should_include_only_visible_layers_by_default() + #_cleanup_hide_unused_nodes.button_pressed = config.is_set_visible_track_automatically_enabled() + _load_default_config() + + +func _set_source(source): + _source = source + _source_field.text = _source + _source_field.tooltip_text = _source + + +func _set_layer(layer): + _layer = layer + _layer_field.add_item(_layer) + + +func _set_slice(slice): + _slice = slice + _slice_field.add_item(_slice) + + +func _set_out_folder(path): + _output_folder = path + _out_folder_field.text = _output_folder if _output_folder != "" else _out_folder_default + _out_folder_field.tooltip_text = _out_folder_field.text + + +func _toggle_section_visibility(key: String) -> void: + _interface_section_state[key] = not _interface_section_state.get(key, false) + _adjust_section_visibility(key) + wizard_config.save_interface_config(target_node, _interface_section_state) + + +func _adjust_section_visibility(key: String) -> void: + var section = _expandable_sections[key] + var is_visible = _interface_section_state.get(key, false) + _adjust_icon(section.header, is_visible) + section.content.visible = is_visible + + +func _adjust_icon(section: Button, is_visible: bool = true) -> void: + var icon_name = "GuiTreeArrowDown" if is_visible else "GuiTreeArrowRight" + section.icon = get_theme_icon(icon_name, "EditorIcons") + + +func _setup_field_listeners(): + _layer_section_header.button_down.connect(_on_layer_header_button_down) + _slice_section_header.button_down.connect(_on_slice_header_button_down) + _output_section_header.button_down.connect(_on_output_header_button_down) + + _source_field.pressed.connect(_on_source_pressed) + _source_field.aseprite_file_dropped.connect(_on_source_aseprite_file_dropped) + + _layer_field.button_down.connect(_on_layer_button_down) + _layer_field.item_selected.connect(_on_layer_item_selected) + + _slice_field.button_down.connect(_on_slice_button_down) + _slice_field.item_selected.connect(_on_slice_item_selected) + + _out_folder_field.dir_dropped.connect(_on_out_dir_dropped) + _out_folder_field.pressed.connect(_on_out_folder_pressed) + + _import_button.pressed.connect(_on_import_pressed) + + +func _on_layer_header_button_down(): + _toggle_section_visibility(INTERFACE_SECTION_KEY_LAYER) + + +func _on_slice_header_button_down(): + _toggle_section_visibility(INTERFACE_SECTION_KEY_SLICE) + + +func _on_output_header_button_down(): + _toggle_section_visibility(INTERFACE_SECTION_KEY_OUTPUT) + + +func _on_layer_button_down(): + if _source == "": + _show_message("Please. Select source file first.") + return + + var layers = _get_available_layers(ProjectSettings.globalize_path(_source)) + _populate_options_field(_layer_field, layers, _layer) + + +func _on_layer_item_selected(index): + if index == 0: + _layer = "" + return + _layer = _layer_field.get_item_text(index) + _save_config() + + +func _on_slice_item_selected(index): + if index == 0: + _slice = "" + return + _slice = _slice_field.get_item_text(index) + _save_config() + + +func _on_slice_button_down(): + if _source == "": + _show_message("Please, select source file first.") + return + + var slices = _get_available_slices(ProjectSettings.globalize_path(_source)) + var current = 0 + _slice_field.clear() + _slice_field.add_item(_layer_default) + + for s in slices: + if s == "": + continue + + _slice_field.add_item(s) + if s == _slice: + current = _slice_field.get_item_count() - 1 + _slice_field.select(current) + + +func _on_source_pressed(): + _open_source_dialog() + +## +## Save current import options to node metadata +## +func _save_config(): + var child_config = _get_current_field_values() + + var cfg := { + "source": _source, + "layer": _layer, + "slice": _slice, + "o_folder": _output_folder, + "o_name": _out_filename_field.text, + "only_visible": _visible_layers_field.button_pressed, + "o_ex_p": _ex_pattern_field.text, + } + + for c in child_config: + cfg[c] = child_config[c] + + wizard_config.save_config(target_node, cfg) + + +func _get_import_options(default_folder: String): + return { + "output_folder": _output_folder if _output_folder != "" else default_folder, + "exception_pattern": _ex_pattern_field.text, + "only_visible_layers": _visible_layers_field.button_pressed, + "output_filename": _out_filename_field.text, + "layer": _layer + } + + +func _open_source_dialog(): + _file_dialog_aseprite = _create_aseprite_file_selection() + get_parent().add_child(_file_dialog_aseprite) + if _source != "": + _file_dialog_aseprite.current_dir = ProjectSettings.globalize_path(_source.get_base_dir()) + _file_dialog_aseprite.popup_centered_ratio() + + +func _create_aseprite_file_selection(): + var file_dialog = EditorFileDialog.new() + file_dialog.file_mode = EditorFileDialog.FILE_MODE_OPEN_FILE + file_dialog.access = EditorFileDialog.ACCESS_FILESYSTEM + file_dialog.connect("file_selected",Callable(self,"_on_aseprite_file_selected")) + file_dialog.set_filters(PackedStringArray(["*.ase","*.aseprite"])) + return file_dialog + + +func _on_aseprite_file_selected(path): + _set_source(ProjectSettings.localize_path(path)) + _save_config() + _file_dialog_aseprite.queue_free() + + +func _on_source_aseprite_file_dropped(path): + _set_source(path) + _save_config() + + +## Helper method to populate field with values +func _populate_options_field(field: OptionButton, values: Array, current_name: String): + var current = 0 + field.clear() + field.add_item("[all]") + + for v in values: + if v == "": + continue + + field.add_item(v) + if v == current_name: + current = field.get_item_count() - 1 + field.select(current) + + +func _on_out_folder_pressed(): + _output_folder_dialog = _create_output_folder_selection() + get_parent().add_child(_output_folder_dialog) + if _output_folder != _out_folder_default: + _output_folder_dialog.current_dir = _output_folder + _output_folder_dialog.popup_centered_ratio() + + +func _create_output_folder_selection(): + var file_dialog = EditorFileDialog.new() + file_dialog.file_mode = EditorFileDialog.FILE_MODE_OPEN_DIR + file_dialog.access = EditorFileDialog.ACCESS_RESOURCES + file_dialog.connect("dir_selected",Callable(self,"_on_output_folder_selected")) + return file_dialog + + +func _on_output_folder_selected(path): + _set_out_folder(path) + _output_folder_dialog.queue_free() + + +func _on_out_dir_dropped(path): + _set_out_folder(path) + + +func _show_message(message: String): + var _warning_dialog = AcceptDialog.new() + get_parent().add_child(_warning_dialog) + _warning_dialog.dialog_text = message + _warning_dialog.popup_centered() + _warning_dialog.connect("popup_hide",Callable(_warning_dialog,"queue_free")) + + +func _notify_aseprite_error(aseprite_error_code): + var error = result_code.get_error_message(aseprite_error_code) + printerr(error) + _show_message(error) + + +func _handle_cleanup(aseprite_content): + if config.should_remove_source_files(): + DirAccess.remove_absolute(aseprite_content.data_file) + file_system.call_deferred("scan") + + +func _on_import_pressed(): + if _importing: + return + _importing = true + + if _source == "": + _show_message("Aseprite file not selected") + _importing = false + return + + await _do_import() + _importing = false + $dock_fields.hide_source_change_warning() + EditorInterface.save_scene() + + +# This is a little bit leaky as this base scene contains fields only relevant to animation players. +# However, this is the simplest thing I can do without overcomplicating stuff. +func _hide_fields(): + $dock_fields/VBoxContainer/modes.hide() + $dock_fields/VBoxContainer/animation_player.hide() + $dock_fields/VBoxContainer/extra/sections/animation.hide() + + +## this will be called before base class does its setup +func _pre_setup(): + pass + + +## this will be called after base class setup is complete +func _setup(): + pass + + +func _load_default_config(): + pass + + +func _load_config(cfg: Dictionary): + pass + + +## Override to return available layers +func _get_available_layers(global_source_path: String) -> Array: + return [] + + +## Override to return available slices +func _get_available_slices(global_source_path: String) -> Array: + return [] + + +## Override this method for extra import options to add to node metadata +func _get_current_field_values() -> Dictionary: + return {} + + +func _do_import(): + pass + + +func _show_specific_fields() -> void: + pass diff --git a/addons/AsepriteWizard/interface/docks/base_inspector_dock.tscn b/addons/AsepriteWizard/interface/docks/base_inspector_dock.tscn new file mode 100644 index 0000000..2c2166a --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/base_inspector_dock.tscn @@ -0,0 +1,345 @@ +[gd_scene load_steps=8 format=3 uid="uid://ljeu0l1ld6v5"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/base_inspector_dock.gd" id="1_0bpq8"] +[ext_resource type="PackedScene" uid="uid://x1f1t87m582u" path="res://addons/AsepriteWizard/interface/shared/animation_player_drop_button.tscn" id="2_pge1b"] +[ext_resource type="PackedScene" uid="uid://dj1uo3blocr8e" path="res://addons/AsepriteWizard/interface/shared/source_drop_button.tscn" id="3_nt1oj"] +[ext_resource type="PackedScene" uid="uid://cwvgnm3o7eed2" path="res://addons/AsepriteWizard/interface/shared/dir_drop_button.tscn" id="4_r7t2l"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_x6usu"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.225, 0.225, 0.225, 0.6) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 +corner_detail = 5 + +[sub_resource type="Image" id="Image_46k7x"] +data = { +"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0), +"format": "RGBA8", +"height": 16, +"mipmaps": false, +"width": 16 +} + +[sub_resource type="ImageTexture" id="ImageTexture_dxtgh"] +image = SubResource("Image_46k7x") + +[node name="base_inspector_dock" type="PanelContainer"] +offset_right = 14.0 +offset_bottom = 14.0 +script = ExtResource("1_0bpq8") + +[node name="margin" type="MarginContainer" parent="."] +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="margin"] +layout_mode = 2 + +[node name="title" type="Button" parent="margin/VBoxContainer"] +layout_mode = 2 +focus_mode = 0 +theme_override_styles/normal = SubResource("StyleBoxFlat_x6usu") +button_mask = 0 +text = "Aseprite" + +[node name="section_title" type="PanelContainer" parent="margin/VBoxContainer"] +visible = false +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="HBoxContainer" type="HBoxContainer" parent="margin/VBoxContainer/section_title"] +layout_mode = 2 +alignment = 1 + +[node name="icon" type="TextureRect" parent="margin/VBoxContainer/section_title/HBoxContainer"] +custom_minimum_size = Vector2(16, 16) +layout_mode = 2 + +[node name="title" type="Label" parent="margin/VBoxContainer/section_title/HBoxContainer"] +layout_mode = 2 +text = "Aseprite" +horizontal_alignment = 1 + +[node name="modes" type="HBoxContainer" parent="margin/VBoxContainer"] +layout_mode = 2 +tooltip_text = "Import mode. +Animation mode (default): set spritesheet as texture and imports animations to the selected AnimationPlayer. +Image mode: Import only first frame and set as texture." + +[node name="Label" type="Label" parent="margin/VBoxContainer/modes"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Mode" + +[node name="options" type="OptionButton" parent="margin/VBoxContainer/modes"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +item_count = 2 +selected = 0 +popup/item_0/text = "Animation" +popup/item_0/id = 0 +popup/item_1/text = "Image" +popup/item_1/id = 1 + +[node name="animation_player" type="HBoxContainer" parent="margin/VBoxContainer"] +layout_mode = 2 +tooltip_text = "AnimationPlayer node where animations should be added to." + +[node name="Label" type="Label" parent="margin/VBoxContainer/animation_player"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "AnimationPlayer" + +[node name="options" parent="margin/VBoxContainer/animation_player" instance=ExtResource("2_pge1b")] +layout_mode = 2 + +[node name="source" type="HBoxContainer" parent="margin/VBoxContainer"] +layout_mode = 2 +tooltip_text = "Location of the Aseprite (*.ase, *.aseprite) source file." + +[node name="Label" type="Label" parent="margin/VBoxContainer/source"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Aseprite File" + +[node name="button" parent="margin/VBoxContainer/source" instance=ExtResource("3_nt1oj")] +layout_mode = 2 + +[node name="extra" type="MarginContainer" parent="margin/VBoxContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 10 + +[node name="sections" type="VBoxContainer" parent="margin/VBoxContainer/extra"] +layout_mode = 2 + +[node name="layers" type="VBoxContainer" parent="margin/VBoxContainer/extra/sections"] +layout_mode = 2 + +[node name="section_header" type="Button" parent="margin/VBoxContainer/extra/sections/layers"] +layout_mode = 2 +focus_mode = 0 +text = "Layers" +icon = SubResource("ImageTexture_dxtgh") +alignment = 0 + +[node name="section_content" type="MarginContainer" parent="margin/VBoxContainer/extra/sections/layers"] +visible = false +layout_mode = 2 +theme_override_constants/margin_left = 10 + +[node name="content" type="VBoxContainer" parent="margin/VBoxContainer/extra/sections/layers/section_content"] +layout_mode = 2 + +[node name="layer" type="HBoxContainer" parent="margin/VBoxContainer/extra/sections/layers/section_content/content"] +layout_mode = 2 +tooltip_text = "Aseprite layer to be used in the animation. By default all layers are included." + +[node name="Label" type="Label" parent="margin/VBoxContainer/extra/sections/layers/section_content/content/layer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Layer" + +[node name="options" type="OptionButton" parent="margin/VBoxContainer/extra/sections/layers/section_content/content/layer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +item_count = 1 +selected = 0 +popup/item_0/text = "[all]" +popup/item_0/id = 0 + +[node name="ex_pattern" type="HBoxContainer" parent="margin/VBoxContainer/extra/sections/layers/section_content/content"] +layout_mode = 2 +tooltip_text = "Exclude layers with name matching this pattern (regex)." + +[node name="Label" type="Label" parent="margin/VBoxContainer/extra/sections/layers/section_content/content/ex_pattern"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Exclude Pattern" + +[node name="LineEdit" type="LineEdit" parent="margin/VBoxContainer/extra/sections/layers/section_content/content/ex_pattern"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 + +[node name="visible_layers" type="HBoxContainer" parent="margin/VBoxContainer/extra/sections/layers/section_content/content"] +layout_mode = 2 +tooltip_text = "If active, layers not visible in the source file won't be included in the final image." + +[node name="Label" type="Label" parent="margin/VBoxContainer/extra/sections/layers/section_content/content/visible_layers"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Only Visible Layers" + +[node name="CheckBox" type="CheckBox" parent="margin/VBoxContainer/extra/sections/layers/section_content/content/visible_layers"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "On" + +[node name="slices" type="VBoxContainer" parent="margin/VBoxContainer/extra/sections"] +layout_mode = 2 + +[node name="section_header" type="Button" parent="margin/VBoxContainer/extra/sections/slices"] +layout_mode = 2 +focus_mode = 0 +text = "Slices" +icon = SubResource("ImageTexture_dxtgh") +alignment = 0 + +[node name="section_content" type="MarginContainer" parent="margin/VBoxContainer/extra/sections/slices"] +visible = false +layout_mode = 2 +theme_override_constants/margin_left = 10 + +[node name="content" type="VBoxContainer" parent="margin/VBoxContainer/extra/sections/slices/section_content"] +layout_mode = 2 + +[node name="slice" type="HBoxContainer" parent="margin/VBoxContainer/extra/sections/slices/section_content/content"] +layout_mode = 2 +tooltip_text = "Aseprite slice to be used in the animation. By default, the whole file is included." + +[node name="Label" type="Label" parent="margin/VBoxContainer/extra/sections/slices/section_content/content/slice"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Slice" + +[node name="options" type="OptionButton" parent="margin/VBoxContainer/extra/sections/slices/section_content/content/slice"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +item_count = 1 +selected = 0 +popup/item_0/text = "[all]" +popup/item_0/id = 0 + +[node name="animation" type="VBoxContainer" parent="margin/VBoxContainer/extra/sections"] +layout_mode = 2 + +[node name="section_header" type="Button" parent="margin/VBoxContainer/extra/sections/animation"] +layout_mode = 2 +focus_mode = 0 +text = "Animation" +icon = SubResource("ImageTexture_dxtgh") +alignment = 0 + +[node name="section_content" type="MarginContainer" parent="margin/VBoxContainer/extra/sections/animation"] +visible = false +layout_mode = 2 +theme_override_constants/margin_left = 10 + +[node name="content" type="VBoxContainer" parent="margin/VBoxContainer/extra/sections/animation/section_content"] +layout_mode = 2 + +[node name="keep_length" type="HBoxContainer" parent="margin/VBoxContainer/extra/sections/animation/section_content/content"] +layout_mode = 2 +tooltip_text = "When this is active the animation length won't be adjusted if other properties were added and the resulting imported animation is shorter." + +[node name="Label" type="Label" parent="margin/VBoxContainer/extra/sections/animation/section_content/content/keep_length"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Keep Manual Animation Length" + +[node name="CheckBox" type="CheckBox" parent="margin/VBoxContainer/extra/sections/animation/section_content/content/keep_length"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "On" + +[node name="auto_visible_track" type="HBoxContainer" parent="margin/VBoxContainer/extra/sections/animation/section_content/content"] +layout_mode = 2 +tooltip_text = "If active, it will automatically determine unused Sprite2D and Sprite3D nodes in each animation and hide them." + +[node name="Label" type="Label" parent="margin/VBoxContainer/extra/sections/animation/section_content/content/auto_visible_track"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Hide Unused Sprites" + +[node name="CheckBox" type="CheckBox" parent="margin/VBoxContainer/extra/sections/animation/section_content/content/auto_visible_track"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "On" + +[node name="output" type="VBoxContainer" parent="margin/VBoxContainer/extra/sections"] +layout_mode = 2 + +[node name="section_header" type="Button" parent="margin/VBoxContainer/extra/sections/output"] +layout_mode = 2 +focus_mode = 0 +text = "Output" +icon = SubResource("ImageTexture_dxtgh") +alignment = 0 + +[node name="section_content" type="MarginContainer" parent="margin/VBoxContainer/extra/sections/output"] +visible = false +layout_mode = 2 +theme_override_constants/margin_left = 10 + +[node name="content" type="VBoxContainer" parent="margin/VBoxContainer/extra/sections/output/section_content"] +layout_mode = 2 + +[node name="out_folder" type="HBoxContainer" parent="margin/VBoxContainer/extra/sections/output/section_content/content"] +layout_mode = 2 +tooltip_text = "Location where the spritesheet file should be saved." + +[node name="Label" type="Label" parent="margin/VBoxContainer/extra/sections/output/section_content/content/out_folder"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Output Folder" + +[node name="button" parent="margin/VBoxContainer/extra/sections/output/section_content/content/out_folder" instance=ExtResource("4_r7t2l")] +layout_mode = 2 + +[node name="out_filename" type="HBoxContainer" parent="margin/VBoxContainer/extra/sections/output/section_content/content"] +layout_mode = 2 +tooltip_text = "Base filename for spritesheet. In case the layer option is used, this works as a prefix to the layer name." + +[node name="Label" type="Label" parent="margin/VBoxContainer/extra/sections/output/section_content/content/out_filename"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Output File Name" + +[node name="LineEdit" type="LineEdit" parent="margin/VBoxContainer/extra/sections/output/section_content/content/out_filename"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 + +[node name="import" type="Button" parent="margin/VBoxContainer"] +layout_mode = 2 +text = "Import" + +[connection signal="item_selected" from="margin/VBoxContainer/modes/options" to="." method="_on_modes_item_selected"] +[connection signal="button_down" from="margin/VBoxContainer/animation_player/options" to="." method="_on_options_button_down"] +[connection signal="item_selected" from="margin/VBoxContainer/animation_player/options" to="." method="_on_options_item_selected"] +[connection signal="node_dropped" from="margin/VBoxContainer/animation_player/options" to="." method="_on_animation_player_node_dropped"] +[connection signal="aseprite_file_dropped" from="margin/VBoxContainer/source/button" to="." method="_on_source_aseprite_file_dropped"] +[connection signal="pressed" from="margin/VBoxContainer/source/button" to="." method="_on_source_pressed"] +[connection signal="button_down" from="margin/VBoxContainer/extra/sections/layers/section_header" to="." method="_on_layer_header_button_down"] +[connection signal="button_down" from="margin/VBoxContainer/extra/sections/layers/section_content/content/layer/options" to="." method="_on_layer_button_down"] +[connection signal="item_selected" from="margin/VBoxContainer/extra/sections/layers/section_content/content/layer/options" to="." method="_on_layer_item_selected"] +[connection signal="button_down" from="margin/VBoxContainer/extra/sections/slices/section_header" to="." method="_on_slice_header_button_down"] +[connection signal="button_down" from="margin/VBoxContainer/extra/sections/slices/section_content/content/slice/options" to="." method="_on_slice_button_down"] +[connection signal="item_selected" from="margin/VBoxContainer/extra/sections/slices/section_content/content/slice/options" to="." method="_on_slice_item_selected"] +[connection signal="button_down" from="margin/VBoxContainer/extra/sections/animation/section_header" to="." method="_on_animation_header_button_down"] +[connection signal="button_down" from="margin/VBoxContainer/extra/sections/output/section_header" to="." method="_on_output_header_button_down"] +[connection signal="dir_dropped" from="margin/VBoxContainer/extra/sections/output/section_content/content/out_folder/button" to="." method="_on_out_dir_dropped"] +[connection signal="pressed" from="margin/VBoxContainer/extra/sections/output/section_content/content/out_folder/button" to="." method="_on_out_folder_pressed"] +[connection signal="pressed" from="margin/VBoxContainer/import" to="." method="_on_import_pressed"] diff --git a/addons/AsepriteWizard/interface/docks/dock_fields.gd b/addons/AsepriteWizard/interface/docks/dock_fields.gd new file mode 100644 index 0000000..166d251 --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/dock_fields.gd @@ -0,0 +1,26 @@ +@tool +extends MarginContainer + + +@onready +var _source_changed_warning_container = $VBoxContainer/source_changed_warning +@onready +var _source_changed_warning_icon = $VBoxContainer/source_changed_warning/MarginContainer/HBoxContainer/Icon + + +func _ready(): + var sb = _source_changed_warning_container.get_theme_stylebox("panel") + var color = EditorInterface.get_editor_settings().get_setting("interface/theme/accent_color") + color.a = 0.2 + sb.bg_color = color + + _source_changed_warning_icon.texture = get_theme_icon("NodeInfo", "EditorIcons") + hide_source_change_warning() + + +func show_source_change_warning(): + _source_changed_warning_container.show() + + +func hide_source_change_warning(): + _source_changed_warning_container.hide() diff --git a/addons/AsepriteWizard/interface/docks/dock_fields.tscn b/addons/AsepriteWizard/interface/docks/dock_fields.tscn new file mode 100644 index 0000000..4480ce5 --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/dock_fields.tscn @@ -0,0 +1,370 @@ +[gd_scene load_steps=11 format=3 uid="uid://uxm7b02wry10"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/dock_fields.gd" id="1_f3w00"] +[ext_resource type="PackedScene" uid="uid://x1f1t87m582u" path="res://addons/AsepriteWizard/interface/shared/animation_player_drop_button.tscn" id="1_gh7xg"] +[ext_resource type="PackedScene" uid="uid://dj1uo3blocr8e" path="res://addons/AsepriteWizard/interface/shared/source_drop_button.tscn" id="1_y4qrm"] +[ext_resource type="PackedScene" uid="uid://cwvgnm3o7eed2" path="res://addons/AsepriteWizard/interface/shared/dir_drop_button.tscn" id="2_pypqg"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ptlkd"] +content_margin_left = 4.0 +content_margin_top = 4.0 +content_margin_right = 4.0 +content_margin_bottom = 4.0 +bg_color = Color(0.225, 0.225, 0.225, 0.6) +corner_radius_top_left = 3 +corner_radius_top_right = 3 +corner_radius_bottom_right = 3 +corner_radius_bottom_left = 3 +corner_detail = 5 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_gu58o"] +bg_color = Color(0.44, 0.73, 0.98, 0.2) +border_width_left = 10 +border_color = Color(0.8, 0.8, 0.8, 0) +corner_radius_top_left = 5 +corner_radius_top_right = 5 +corner_radius_bottom_right = 5 +corner_radius_bottom_left = 5 + +[sub_resource type="Image" id="Image_0mhd7"] +data = { +"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 43, 255, 255, 255, 78, 255, 255, 255, 112, 255, 255, 255, 111, 255, 255, 255, 78, 255, 255, 255, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 12, 255, 255, 255, 107, 255, 255, 255, 212, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 210, 255, 255, 255, 105, 255, 255, 255, 10, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 47, 255, 255, 255, 219, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 217, 255, 255, 255, 44, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 84, 255, 255, 255, 247, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 245, 255, 255, 255, 80, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 45, 255, 255, 255, 246, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 244, 255, 255, 255, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 10, 255, 255, 255, 217, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 128, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 128, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 216, 255, 255, 255, 10, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 104, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 128, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 128, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 101, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 211, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 128, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 128, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 207, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 44, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 39, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 81, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 192, 255, 255, 255, 128, 255, 255, 255, 128, 255, 255, 255, 192, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 76, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 114, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 128, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 128, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 110, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 114, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 128, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 128, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 109, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 81, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 128, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 128, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 75, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 43, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 128, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 128, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 37, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 210, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 128, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 128, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 204, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 104, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 128, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 128, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 97, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 10, 255, 255, 255, 217, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 128, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 128, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 212, 255, 255, 255, 8, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 44, 255, 255, 255, 245, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 242, 255, 255, 255, 39, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 81, 255, 255, 255, 245, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 243, 255, 255, 255, 75, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 44, 255, 255, 255, 217, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 214, 255, 255, 255, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 10, 255, 255, 255, 102, 255, 255, 255, 208, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 207, 255, 255, 255, 100, 255, 255, 255, 9, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 38, 255, 255, 255, 75, 255, 255, 255, 110, 255, 255, 255, 110, 255, 255, 255, 75, 255, 255, 255, 38, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0), +"format": "RGBA8", +"height": 24, +"mipmaps": false, +"width": 24 +} + +[sub_resource type="ImageTexture" id="ImageTexture_f74rk"] +image = SubResource("Image_0mhd7") + +[sub_resource type="Image" id="Image_ufn2p"] +data = { +"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0), +"format": "RGBA8", +"height": 16, +"mipmaps": false, +"width": 16 +} + +[sub_resource type="ImageTexture" id="ImageTexture_qqpl1"] +image = SubResource("Image_ufn2p") + +[node name="dock_fields" type="MarginContainer"] +script = ExtResource("1_f3w00") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +layout_mode = 2 + +[node name="title" type="Button" parent="VBoxContainer"] +layout_mode = 2 +focus_mode = 0 +theme_override_styles/normal = SubResource("StyleBoxFlat_ptlkd") +button_mask = 0 +text = "Aseprite" + +[node name="section_title" type="PanelContainer" parent="VBoxContainer"] +visible = false +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/section_title"] +layout_mode = 2 +alignment = 1 + +[node name="icon" type="TextureRect" parent="VBoxContainer/section_title/HBoxContainer"] +custom_minimum_size = Vector2(16, 16) +layout_mode = 2 + +[node name="title" type="Label" parent="VBoxContainer/section_title/HBoxContainer"] +layout_mode = 2 +text = "Aseprite" +horizontal_alignment = 1 + +[node name="modes" type="HBoxContainer" parent="VBoxContainer"] +layout_mode = 2 +tooltip_text = "Import mode. +Animation mode (default): set spritesheet as texture and imports animations to the selected AnimationPlayer. +Image mode: Import only first frame and set as texture." + +[node name="Label" type="Label" parent="VBoxContainer/modes"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Mode" + +[node name="options" type="OptionButton" parent="VBoxContainer/modes"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +item_count = 2 +selected = 0 +popup/item_0/text = "Animation" +popup/item_0/id = 0 +popup/item_1/text = "Image" +popup/item_1/id = 1 + +[node name="animation_player" type="HBoxContainer" parent="VBoxContainer" groups=["aw_animation_player_only"]] +layout_mode = 2 +tooltip_text = "AnimationPlayer node where animations should be added to." + +[node name="Label" type="Label" parent="VBoxContainer/animation_player"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "AnimationPlayer" + +[node name="options" parent="VBoxContainer/animation_player" instance=ExtResource("1_gh7xg")] +layout_mode = 2 + +[node name="source" type="HBoxContainer" parent="VBoxContainer"] +layout_mode = 2 +tooltip_text = "Location of the Aseprite (*.ase, *.aseprite) source file." + +[node name="Label" type="Label" parent="VBoxContainer/source"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Aseprite File" + +[node name="button" parent="VBoxContainer/source" instance=ExtResource("1_y4qrm")] +layout_mode = 2 + +[node name="source_changed_warning" type="PanelContainer" parent="VBoxContainer"] +visible = false +layout_mode = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_gu58o") + +[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/source_changed_warning"] +layout_mode = 2 +theme_override_constants/margin_left = 10 +theme_override_constants/margin_right = 10 + +[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/source_changed_warning/MarginContainer"] +layout_mode = 2 + +[node name="Icon" type="TextureRect" parent="VBoxContainer/source_changed_warning/MarginContainer/HBoxContainer"] +modulate = Color(1, 1, 1, 0.486275) +custom_minimum_size = Vector2(16, 0) +layout_mode = 2 +texture = SubResource("ImageTexture_f74rk") +stretch_mode = 5 + +[node name="Label" type="Label" parent="VBoxContainer/source_changed_warning/MarginContainer/HBoxContainer"] +custom_minimum_size = Vector2(10, 0) +layout_mode = 2 +size_flags_horizontal = 3 +text = "Source file changed since last import" +autowrap_mode = 2 + +[node name="extra" type="MarginContainer" parent="VBoxContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 10 + +[node name="sections" type="VBoxContainer" parent="VBoxContainer/extra"] +layout_mode = 2 + +[node name="layers" type="VBoxContainer" parent="VBoxContainer/extra/sections"] +layout_mode = 2 + +[node name="section_header" type="Button" parent="VBoxContainer/extra/sections/layers"] +layout_mode = 2 +focus_mode = 0 +text = "Layers" +icon = SubResource("ImageTexture_qqpl1") +alignment = 0 + +[node name="section_content" type="MarginContainer" parent="VBoxContainer/extra/sections/layers"] +visible = false +layout_mode = 2 +theme_override_constants/margin_left = 10 + +[node name="content" type="VBoxContainer" parent="VBoxContainer/extra/sections/layers/section_content"] +layout_mode = 2 + +[node name="layer" type="HBoxContainer" parent="VBoxContainer/extra/sections/layers/section_content/content"] +layout_mode = 2 +tooltip_text = "Aseprite layer to be used in the animation. By default all layers are included." + +[node name="Label" type="Label" parent="VBoxContainer/extra/sections/layers/section_content/content/layer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Layer" + +[node name="options" type="OptionButton" parent="VBoxContainer/extra/sections/layers/section_content/content/layer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +item_count = 1 +selected = 0 +popup/item_0/text = "[all]" +popup/item_0/id = 0 + +[node name="ex_pattern" type="HBoxContainer" parent="VBoxContainer/extra/sections/layers/section_content/content"] +layout_mode = 2 +tooltip_text = "Exclude layers with name matching this pattern (regex)." + +[node name="Label" type="Label" parent="VBoxContainer/extra/sections/layers/section_content/content/ex_pattern"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Exclude Pattern" + +[node name="LineEdit" type="LineEdit" parent="VBoxContainer/extra/sections/layers/section_content/content/ex_pattern"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 + +[node name="visible_layers" type="HBoxContainer" parent="VBoxContainer/extra/sections/layers/section_content/content"] +layout_mode = 2 +tooltip_text = "If active, layers not visible in the source file won't be included in the final image." + +[node name="Label" type="Label" parent="VBoxContainer/extra/sections/layers/section_content/content/visible_layers"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Only Visible Layers" + +[node name="CheckBox" type="CheckBox" parent="VBoxContainer/extra/sections/layers/section_content/content/visible_layers"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "On" + +[node name="slices" type="VBoxContainer" parent="VBoxContainer/extra/sections"] +layout_mode = 2 + +[node name="section_header" type="Button" parent="VBoxContainer/extra/sections/slices"] +layout_mode = 2 +focus_mode = 0 +text = "Slices" +icon = SubResource("ImageTexture_qqpl1") +alignment = 0 + +[node name="section_content" type="MarginContainer" parent="VBoxContainer/extra/sections/slices"] +visible = false +layout_mode = 2 +theme_override_constants/margin_left = 10 + +[node name="content" type="VBoxContainer" parent="VBoxContainer/extra/sections/slices/section_content"] +layout_mode = 2 + +[node name="slice" type="HBoxContainer" parent="VBoxContainer/extra/sections/slices/section_content/content"] +layout_mode = 2 +tooltip_text = "Aseprite slice to be used in the animation. By default, the whole file is included." + +[node name="Label" type="Label" parent="VBoxContainer/extra/sections/slices/section_content/content/slice"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Slice" + +[node name="options" type="OptionButton" parent="VBoxContainer/extra/sections/slices/section_content/content/slice"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +item_count = 1 +selected = 0 +popup/item_0/text = "[all]" +popup/item_0/id = 0 + +[node name="animation" type="VBoxContainer" parent="VBoxContainer/extra/sections" groups=["aw_animation_player_only"]] +layout_mode = 2 + +[node name="section_header" type="Button" parent="VBoxContainer/extra/sections/animation"] +layout_mode = 2 +focus_mode = 0 +text = "Animation" +icon = SubResource("ImageTexture_qqpl1") +alignment = 0 + +[node name="section_content" type="MarginContainer" parent="VBoxContainer/extra/sections/animation"] +visible = false +layout_mode = 2 +theme_override_constants/margin_left = 10 + +[node name="content" type="VBoxContainer" parent="VBoxContainer/extra/sections/animation/section_content"] +layout_mode = 2 + +[node name="keep_length" type="HBoxContainer" parent="VBoxContainer/extra/sections/animation/section_content/content"] +layout_mode = 2 +tooltip_text = "When this is active the animation length won't be adjusted if other properties were added and the resulting imported animation is shorter." + +[node name="Label" type="Label" parent="VBoxContainer/extra/sections/animation/section_content/content/keep_length"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Keep Manual Animation Length" + +[node name="CheckBox" type="CheckBox" parent="VBoxContainer/extra/sections/animation/section_content/content/keep_length"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "On" + +[node name="auto_visible_track" type="HBoxContainer" parent="VBoxContainer/extra/sections/animation/section_content/content"] +layout_mode = 2 +tooltip_text = "If active, it will automatically determine unused Sprite2D and Sprite3D nodes in each animation and hide them." + +[node name="Label" type="Label" parent="VBoxContainer/extra/sections/animation/section_content/content/auto_visible_track"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Hide Unused Sprites" + +[node name="CheckBox" type="CheckBox" parent="VBoxContainer/extra/sections/animation/section_content/content/auto_visible_track"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "On" + +[node name="output" type="VBoxContainer" parent="VBoxContainer/extra/sections"] +layout_mode = 2 + +[node name="section_header" type="Button" parent="VBoxContainer/extra/sections/output"] +layout_mode = 2 +focus_mode = 0 +text = "Output" +icon = SubResource("ImageTexture_qqpl1") +alignment = 0 + +[node name="section_content" type="MarginContainer" parent="VBoxContainer/extra/sections/output"] +visible = false +layout_mode = 2 +theme_override_constants/margin_left = 10 + +[node name="content" type="VBoxContainer" parent="VBoxContainer/extra/sections/output/section_content"] +layout_mode = 2 + +[node name="out_folder" type="HBoxContainer" parent="VBoxContainer/extra/sections/output/section_content/content"] +layout_mode = 2 +tooltip_text = "Location where the spritesheet file should be saved." + +[node name="Label" type="Label" parent="VBoxContainer/extra/sections/output/section_content/content/out_folder"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Output Folder" + +[node name="button" parent="VBoxContainer/extra/sections/output/section_content/content/out_folder" instance=ExtResource("2_pypqg")] +layout_mode = 2 + +[node name="out_filename" type="HBoxContainer" parent="VBoxContainer/extra/sections/output/section_content/content"] +layout_mode = 2 +tooltip_text = "Base filename for spritesheet. In case the layer option is used, this works as a prefix to the layer name." + +[node name="Label" type="Label" parent="VBoxContainer/extra/sections/output/section_content/content/out_filename"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Output File Name" + +[node name="LineEdit" type="LineEdit" parent="VBoxContainer/extra/sections/output/section_content/content/out_filename"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 + +[node name="import" type="Button" parent="VBoxContainer"] +layout_mode = 2 +text = "Import" diff --git a/addons/AsepriteWizard/interface/docks/sprite/inspector_plugin.gd b/addons/AsepriteWizard/interface/docks/sprite/inspector_plugin.gd new file mode 100644 index 0000000..ff3d617 --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/sprite/inspector_plugin.gd @@ -0,0 +1,14 @@ +@tool +extends EditorInspectorPlugin + +const APInspectorDock = preload("./sprite_inspector_dock.tscn") + + +func _can_handle(object): + return object is Sprite2D || object is Sprite3D || object is TextureRect + + +func _parse_end(object): + var dock = APInspectorDock.instantiate() + dock.target_node = object + add_custom_control(dock) diff --git a/addons/AsepriteWizard/interface/docks/sprite/sprite_inspector_dock.gd b/addons/AsepriteWizard/interface/docks/sprite/sprite_inspector_dock.gd new file mode 100644 index 0000000..740b5d5 --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/sprite/sprite_inspector_dock.gd @@ -0,0 +1,255 @@ +@tool +extends "../base_inspector_dock.gd" + +const AnimationCreator = preload("../../../creators/animation_player/animation_creator.gd") +const SpriteAnimationCreator = preload("../../../creators/animation_player/sprite_animation_creator.gd") +const TextureRectAnimationCreator = preload("../../../creators/animation_player/texture_rect_animation_creator.gd") +const StaticTextureCreator = preload("../../../creators/static_texture/texture_creator.gd") + +enum ImportMode { + ANIMATION = 0, + IMAGE = 1 +} + +var animation_creator: AnimationCreator +var static_texture_creator: StaticTextureCreator + +var _import_mode = -1 +var _animation_player_path: String + +@onready var _import_mode_options_field := $dock_fields/VBoxContainer/modes/options as OptionButton +@onready var _animation_player_field := $dock_fields/VBoxContainer/animation_player/options as OptionButton +@onready var _animation_player_container := $dock_fields/VBoxContainer/animation_player as HBoxContainer + +# animation +@onready var _animation_section := $dock_fields/VBoxContainer/extra/sections/animation as VBoxContainer +@onready var _animation_section_header := $dock_fields/VBoxContainer/extra/sections/animation/section_header as Button +@onready var _animation_section_container := $dock_fields/VBoxContainer/extra/sections/animation/section_content as MarginContainer +@onready var _cleanup_hide_unused_nodes := $dock_fields/VBoxContainer/extra/sections/animation/section_content/content/auto_visible_track/CheckBox as CheckBox +@onready var _keep_length := $dock_fields/VBoxContainer/extra/sections/animation/section_content/content/keep_length/CheckBox as CheckBox + +const INTERFACE_SECTION_KEY_ANIMATION = "animation_section" + + +func _pre_setup(): + _expandable_sections[INTERFACE_SECTION_KEY_ANIMATION] = { "header": _animation_section_header, "content": _animation_section_container} + + +func _setup(): + if target_node is Sprite2D || target_node is Sprite3D: + animation_creator = SpriteAnimationCreator.new() + if target_node is TextureRect: + animation_creator = TextureRectAnimationCreator.new() + + static_texture_creator = StaticTextureCreator.new() + + _setup_animation_fields_listeners() + + +func _load_config(cfg): + if cfg.has("player"): + _animation_player_field.clear() + _set_animation_player(cfg.player) + + _cleanup_hide_unused_nodes.button_pressed = cfg.get("set_vis_track", config.is_set_visible_track_automatically_enabled()) + _keep_length.button_pressed = cfg.get("keep_anim_length", false) + _set_import_mode(int(cfg.get("i_mode", 0))) + + +func _load_default_config(): + _cleanup_hide_unused_nodes.button_pressed = config.is_set_visible_track_automatically_enabled() + + +func _set_animation_player(player): + _animation_player_path = player + _animation_player_field.add_item(_animation_player_path) + + +func _set_import_mode(import_mode): + if _import_mode == import_mode: + return + + _import_mode = import_mode + var index = _import_mode_options_field.get_item_index(import_mode) + _import_mode_options_field.select(index) + _handle_import_mode() + + +func _handle_import_mode(): + match _import_mode: + ImportMode.ANIMATION: + _animation_player_container.show() + _animation_section.show() + ImportMode.IMAGE: + _animation_player_container.hide() + _animation_section.hide() + + +func _setup_animation_fields_listeners(): + _animation_section_header.button_down.connect(_on_animation_header_button_down) + _animation_player_field.node_dropped.connect(_on_animation_player_node_dropped) + _animation_player_field.button_down.connect(_on_animation_player_button_down) + _animation_player_field.item_selected.connect(_on_animation_player_item_selected) + + _import_mode_options_field.item_selected.connect(_on_modes_item_selected) + + +func _on_animation_player_button_down(): + _refresh_animation_players() + + +func _refresh_animation_players(): + var animation_players = [] + var root = get_tree().get_edited_scene_root() + _find_animation_players(root, root, animation_players) + + var current = 0 + _animation_player_field.clear() + _animation_player_field.add_item("[empty]") + + for ap in animation_players: + _animation_player_field.add_item(ap) + if ap.get_concatenated_names() == _animation_player_path: + current = _animation_player_field.get_item_count() - 1 + + _animation_player_field.select(current) + + +func _find_animation_players(root: Node, node: Node, players: Array): + if node is AnimationPlayer: + players.push_back(root.get_path_to(node)) + + for c in node.get_children(): + _find_animation_players(root, c, players) + + +func _on_animation_player_item_selected(index): + if index == 0: + _animation_player_path = "" + return + _animation_player_path = _animation_player_field.get_item_text(index) + _save_config() + + +func _do_import(): + if _import_mode == ImportMode.IMAGE: + await _import_static() + return + + await _import_for_animation_player() + +## +## Import aseprite animations to target AnimationPlayer and set +## spritesheet as the node's texture +## +func _import_for_animation_player(): + var root = get_tree().get_edited_scene_root() + + if _animation_player_path == "" or not root.has_node(_animation_player_path): + _show_message("AnimationPlayer not found") + _importing = false + return + + var source_path = ProjectSettings.globalize_path(_source) + + var options = _get_import_options(root.scene_file_path.get_base_dir()) + + _save_config() + + var aseprite_output = _aseprite_file_exporter.generate_aseprite_file(source_path, options) + + if not aseprite_output.is_ok: + _notify_aseprite_error(aseprite_output.code) + return + + file_system.scan() + await file_system.filesystem_changed + + var anim_options = { + "keep_anim_length": _keep_length.button_pressed, + "cleanup_hide_unused_nodes": _cleanup_hide_unused_nodes.button_pressed, + "slice": _slice, + } + + animation_creator.create_animations(target_node, root.get_node(_animation_player_path), aseprite_output.content, anim_options) + _importing = false + + wizard_config.set_source_hash(target_node, FileAccess.get_md5(source_path)) + _handle_cleanup(aseprite_output.content) + +## +## Import first frame from aseprite file as node texture +## +func _import_static(): + var source_path = ProjectSettings.globalize_path(_source) + var root = get_tree().get_edited_scene_root() + + var options = _get_import_options(root.scene_file_path.get_base_dir()) + options["first_frame_only"] = true + + _save_config() + + var aseprite_output = _aseprite_file_exporter.generate_aseprite_file(source_path, options) + + if not aseprite_output.is_ok: + _notify_aseprite_error(aseprite_output.code) + return + + file_system.scan() + await file_system.filesystem_changed + + static_texture_creator.load_texture(target_node, aseprite_output.content, { "slice": _slice }) + + _importing = false + wizard_config.set_source_hash(target_node, FileAccess.get_md5(source_path)) + _handle_cleanup(aseprite_output.content) + + +func _get_current_field_values() -> Dictionary: + var cfg := { + "i_mode": _import_mode, + "player": _animation_player_path, + "keep_anim_length": _keep_length.button_pressed, + } + + if _cleanup_hide_unused_nodes.button_pressed != config.is_set_visible_track_automatically_enabled(): + cfg["set_vis_track"] = _cleanup_hide_unused_nodes.button_pressed + + return cfg + + +func _get_available_layers(global_source_path: String) -> Array: + return animation_creator.list_layers(global_source_path) + + +func _get_available_slices(global_source_path: String) -> Array: + return animation_creator.list_slices(global_source_path) + + +func _on_animation_player_node_dropped(node_path): + var node = get_node(node_path) + var root = get_tree().get_edited_scene_root() + + _animation_player_path = root.get_path_to(node) + + for i in range(_animation_player_field.get_item_count()): + if _animation_player_field.get_item_text(i) == _animation_player_path: + _animation_player_field.select(i) + break + _save_config() + + +func _on_modes_item_selected(index): + var id = _import_mode_options_field.get_item_id(index) + _import_mode = id + _handle_import_mode() + + +func _on_animation_header_button_down(): + _toggle_section_visibility(INTERFACE_SECTION_KEY_ANIMATION) + + +func _show_specific_fields(): + _import_mode_options_field.get_parent().show() + _animation_player_container.show() + _animation_section.show() diff --git a/addons/AsepriteWizard/interface/docks/sprite/sprite_inspector_dock.tscn b/addons/AsepriteWizard/interface/docks/sprite/sprite_inspector_dock.tscn new file mode 100644 index 0000000..1d8e10b --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/sprite/sprite_inspector_dock.tscn @@ -0,0 +1,12 @@ +[gd_scene load_steps=3 format=3 uid="uid://biyshalfalqqw"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/sprite/sprite_inspector_dock.gd" id="1_ku6wj"] +[ext_resource type="PackedScene" uid="uid://uxm7b02wry10" path="res://addons/AsepriteWizard/interface/docks/dock_fields.tscn" id="2_wkqx4"] + +[node name="sprite_inspector_dock" type="PanelContainer"] +offset_right = 14.0 +offset_bottom = 14.0 +script = ExtResource("1_ku6wj") + +[node name="dock_fields" parent="." instance=ExtResource("2_wkqx4")] +layout_mode = 2 diff --git a/addons/AsepriteWizard/interface/docks/wizard/as_wizard_dock_container.gd b/addons/AsepriteWizard/interface/docks/wizard/as_wizard_dock_container.gd new file mode 100644 index 0000000..8b62224 --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/as_wizard_dock_container.gd @@ -0,0 +1,39 @@ +@tool +extends TabContainer + +signal close_requested + +const WizardWindow = preload("./as_wizard_window.tscn") + +func _ready(): + $Import.close_requested.connect(emit_signal.bind("close_requested")) + $Import.import_success.connect(_on_import_success) + $History.request_edit.connect(_on_edit_request) + $History.request_import.connect(_on_import_request) + $ImportedSpriteFrames.import_success.connect($History.add_entry) + + self.set_tab_title(1, "Imported Resources") + + +func _on_AsWizardDockContainer_tab_changed(tab: int): + match tab: + 1: + $ImportedSpriteFrames.init_resources() + 2: + $History.reload() + + +func _on_edit_request(import_cfg: Dictionary): + $Import.load_import_config(import_cfg) + self.current_tab = 0 + + +func _on_import_request(import_cfg: Dictionary): + $Import.load_import_config(import_cfg) + $Import.trigger_import() + + +func _on_import_success(settings: Dictionary): + $ImportedSpriteFrames.init_resources() + $ImportedSpriteFrames.reload_tree() + $History.add_entry(settings) diff --git a/addons/AsepriteWizard/interface/docks/wizard/as_wizard_dock_container.tscn b/addons/AsepriteWizard/interface/docks/wizard/as_wizard_dock_container.tscn new file mode 100644 index 0000000..9201fdb --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/as_wizard_dock_container.tscn @@ -0,0 +1,28 @@ +[gd_scene load_steps=5 format=3 uid="uid://b844j1tk3vxer"] + +[ext_resource type="PackedScene" uid="uid://c5dwobjd34w3p" path="res://addons/AsepriteWizard/interface/docks/wizard/as_wizard_window.tscn" id="1"] +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/as_wizard_dock_container.gd" id="2"] +[ext_resource type="PackedScene" uid="uid://cyoin5ncul0fm" path="res://addons/AsepriteWizard/interface/docks/wizard/sprite_frames_import_history.tscn" id="3"] +[ext_resource type="PackedScene" uid="uid://bfhsdslj8kt7b" path="res://addons/AsepriteWizard/interface/docks/wizard/imported_sprite_frames.tscn" id="3_25qb4"] + +[node name="AsWizardDockContainer" type="TabContainer"] +offset_right = 281.0 +offset_bottom = 36.0 +tab_alignment = 2 +use_hidden_tabs_for_min_size = true +script = ExtResource("2") + +[node name="Import" parent="." instance=ExtResource("1")] +layout_mode = 2 +tooltip_text = "SpriteFrames Importer" + +[node name="ImportedSpriteFrames" parent="." instance=ExtResource("3_25qb4")] +visible = false +layout_mode = 2 +metadata/_tab_name = "Imported Resources" + +[node name="History" parent="." instance=ExtResource("3")] +visible = false +layout_mode = 2 + +[connection signal="tab_changed" from="." to="." method="_on_AsWizardDockContainer_tab_changed"] diff --git a/addons/AsepriteWizard/interface/docks/wizard/as_wizard_window.gd b/addons/AsepriteWizard/interface/docks/wizard/as_wizard_window.gd new file mode 100644 index 0000000..46f3130 --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/as_wizard_window.gd @@ -0,0 +1,274 @@ +@tool +extends PanelContainer + +signal close_requested +signal import_success(file_settings) + +const result_code = preload("../../../config/result_codes.gd") +var _config = preload("../../../config/config.gd").new() + +var _import_helper = preload("./wizard_import_helper.gd").new() + +var _file_system: EditorFileSystem = EditorInterface.get_resource_filesystem() + +var _file_dialog_aseprite: EditorFileDialog +var _output_folder_dialog: EditorFileDialog +var _warning_dialog: AcceptDialog + +@onready var _layer_section_btn: Button = $container/options/layer_section/header/section_header +@onready var _layer_section_content: MarginContainer = $container/options/layer_section/section_content + +@onready var _output_section_btn: Button = $container/options/output_section/header/section_header +@onready var _output_section_content: MarginContainer = $container/options/output_section/section_content + +@onready var _file_location_field: LineEdit = $container/options/file_location/HBoxContainer/file_location_path + +@onready var _output_folder_field: LineEdit = $container/options/output_section/section_content/items/output_folder/HBoxContainer/file_location_path + +@onready var _exception_pattern_field: LineEdit = $container/options/layer_section/section_content/items/exclude_pattern/pattern + +@onready var _split_mode_field: CheckBox = $container/options/layer_section/section_content/items/split_layers/field + +@onready var _only_visible_layers_field: CheckBox = $container/options/layer_section/section_content/items/visible_layers/field + +@onready var _custom_name_field: LineEdit = $container/options/output_section/section_content/items/custom_filename/pattern + +@onready var _do_not_create_res_field: CheckBox = $container/options/output_section/section_content/items/disable_resource_creation/field + +const INTERFACE_SECTION_KEY_LAYER = "layer_section" +const INTERFACE_SECTION_KEY_OUTPUT = "output_section" + +@onready var _expandable_sections = { + INTERFACE_SECTION_KEY_LAYER: { "header": _layer_section_btn, "content": _layer_section_content}, + INTERFACE_SECTION_KEY_OUTPUT: { "header": _output_section_btn, "content": _output_section_content}, +} + +var _interface_section_state = {} + + +func _ready(): + _configure_sections() + _load_persisted_config() + + +func _exit_tree(): + if is_instance_valid(_file_dialog_aseprite): + _file_dialog_aseprite.queue_free() + if is_instance_valid(_output_folder_dialog): + _output_folder_dialog.queue_free() + if is_instance_valid(_warning_dialog): + _warning_dialog.queue_free() + + +func _init_aseprite_file_selection_dialog(): + _file_dialog_aseprite = _create_aseprite_file_selection() + get_parent().get_parent().add_child(_file_dialog_aseprite) + + +func _init_output_folder_selection_dialog(): + _output_folder_dialog = _create_outuput_folder_selection() + get_parent().get_parent().add_child(_output_folder_dialog) + + +func _init_warning_dialog(): + _warning_dialog = AcceptDialog.new() + _warning_dialog.exclusive = false + get_parent().get_parent().add_child(_warning_dialog) + + +func _load_persisted_config() -> void: + var cfg = _load_last_import_cfg() + _load_config(cfg) + + +func _load_config(cfg: Dictionary) -> void: + _split_mode_field.button_pressed = cfg.split_layers + _only_visible_layers_field.button_pressed = cfg.only_visible_layers + _exception_pattern_field.text = cfg.layer_exclusion_pattern + _custom_name_field.text = cfg.output_name + _file_location_field.text = cfg.source_file + _do_not_create_res_field.button_pressed = cfg.do_not_create_resource + _output_folder_field.text = cfg.output_location if cfg.output_location != "" else "res://" + + +func _save_config() -> void: + _config.set_standalone_spriteframes_last_import_config(_get_field_values()) + + +func _get_field_values() -> Dictionary: + return { + "split_layers": _split_mode_field.button_pressed, + "only_visible_layers": _only_visible_layers_field.button_pressed, + "layer_exclusion_pattern": _exception_pattern_field.text, + "output_name": _custom_name_field.text, + "source_file": _file_location_field.text, + "do_not_create_resource": _do_not_create_res_field.button_pressed, + "output_location": _output_folder_field.text if _output_folder_field.text != "" else "res://", + } + + +func _clear_config() -> void: + _config.clear_standalone_spriteframes_last_import_config() + var default = _get_default_config() + _load_config(default) + + +## This is used by the other tabs to set the form fields +func load_import_config(field_values: Dictionary): + _split_mode_field.button_pressed = field_values.split_layers + _only_visible_layers_field.button_pressed = field_values.only_visible_layers + _exception_pattern_field.text = field_values.layer_exclusion_pattern + _custom_name_field.text = field_values.output_name + _file_location_field.text = field_values.source_file + _do_not_create_res_field.button_pressed = field_values.do_not_create_resource + _output_folder_field.text = field_values.output_location + + +func _open_aseprite_file_selection_dialog(): + if not is_instance_valid(_file_dialog_aseprite): + _init_aseprite_file_selection_dialog() + + var current_selection = _file_location_field.text + if current_selection != "": + _file_dialog_aseprite.current_dir = current_selection.get_base_dir() + _file_dialog_aseprite.popup_centered_ratio() + + +func _open_output_folder_selection_dialog(): + if not is_instance_valid(_output_folder_dialog): + _init_output_folder_selection_dialog() + var current_selection = _output_folder_field.text + if current_selection != "": + _output_folder_dialog.current_dir = current_selection + _output_folder_dialog.popup_centered_ratio() + + +func _create_aseprite_file_selection(): + var file_dialog = EditorFileDialog.new() + file_dialog.file_mode = EditorFileDialog.FILE_MODE_OPEN_FILE + file_dialog.access = EditorFileDialog.ACCESS_FILESYSTEM + file_dialog.connect("file_selected", _on_aseprite_file_selected) + file_dialog.set_filters(PackedStringArray(["*.ase","*.aseprite"])) + return file_dialog + + +func _create_outuput_folder_selection(): + var file_dialog = EditorFileDialog.new() + file_dialog.file_mode = EditorFileDialog.FILE_MODE_OPEN_DIR + file_dialog.access = EditorFileDialog.ACCESS_RESOURCES + file_dialog.connect("dir_selected", _on_output_folder_selected) + return file_dialog + + +func _on_aseprite_file_selected(path): + var localized_path = ProjectSettings.localize_path(path) + _file_location_field.text = localized_path + + +func _on_output_folder_selected(path): + _output_folder_field.text = path + + +func _on_next_btn_up(): + var aseprite_file = _file_location_field.text + var fields = _get_field_values() + var exit_code = await _import_helper.import_and_create_resources(aseprite_file, _get_field_values()) + + if exit_code != OK: + _show_error(exit_code) + return + + emit_signal("import_success", fields) + + if _config.should_remove_source_files(): + _file_system.call_deferred("scan") + + _show_import_success_message() + + +func trigger_import(): + _on_next_btn_up() + + +func _on_close_btn_up(): + _close_window() + + +func _close_window(): + _save_config() + self.emit_signal("close_requested") + + +func _on_clear_button_up(): + _clear_config() + + +func _show_error(code: int): + _show_error_message(result_code.get_error_message(code)) + + +func _show_error_message(message: String): + if not is_instance_valid(_warning_dialog): + _init_warning_dialog() + _warning_dialog.dialog_text = "Error: %s" % message + _warning_dialog.popup_centered() + + +func _show_import_success_message(): + if not is_instance_valid(_warning_dialog): + _init_warning_dialog() + _warning_dialog.dialog_text = "Aseprite import succeeded" + _warning_dialog.popup_centered() + _save_config() + + +func _configure_sections(): + for key in _expandable_sections: + _adjust_section_visibility(key) + + +func _adjust_section_visibility(key: String) -> void: + var section = _expandable_sections[key] + var is_visible = _interface_section_state.get(key, true) + _adjust_icon(section.header, is_visible) + section.content.visible = is_visible + + +func _adjust_icon(section: Button, is_visible: bool = true) -> void: + var icon_name = "GuiTreeArrowDown" if is_visible else "GuiTreeArrowRight" + section.icon = get_theme_icon(icon_name, "EditorIcons") + + +func _toggle_section_visibility(key: String) -> void: + _interface_section_state[key] = not _interface_section_state.get(key, true) + _adjust_section_visibility(key) + + +func _on_layer_section_header_button_down(): + _toggle_section_visibility(INTERFACE_SECTION_KEY_LAYER) + + +func _on_output_section_header_button_down(): + _toggle_section_visibility(INTERFACE_SECTION_KEY_OUTPUT) + + +func _load_last_import_cfg() -> Dictionary: + var cfg = _config.get_standalone_spriteframes_last_import_config() + + if cfg.is_empty(): + return _get_default_config() + + return cfg + + +func _get_default_config() -> Dictionary: + return { + "split_layers": false, + "only_visible_layers": _config.should_include_only_visible_layers_by_default(), + "layer_exclusion_pattern": _config.get_default_exclusion_pattern(), + "output_name": "", + "source_file": "", + "do_not_create_resource": false, + "output_location": "res://", + } + diff --git a/addons/AsepriteWizard/interface/docks/wizard/as_wizard_window.tscn b/addons/AsepriteWizard/interface/docks/wizard/as_wizard_window.tscn new file mode 100644 index 0000000..a9f901b --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/as_wizard_window.tscn @@ -0,0 +1,204 @@ +[gd_scene load_steps=4 format=3 uid="uid://c5dwobjd34w3p"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/as_wizard_window.gd" id="1"] + +[sub_resource type="Image" id="Image_wk0kh"] +data = { +"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 44, 255, 255, 255, 90, 255, 255, 255, 52, 255, 255, 255, 1, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 1, 255, 255, 255, 52, 255, 255, 255, 90, 255, 255, 255, 44, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 90, 255, 255, 255, 102, 255, 255, 255, 102, 255, 255, 255, 61, 255, 255, 255, 1, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 1, 255, 255, 255, 61, 255, 255, 255, 102, 255, 255, 255, 102, 255, 255, 255, 90, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 52, 255, 255, 255, 102, 255, 255, 255, 102, 255, 255, 255, 102, 255, 255, 255, 62, 255, 255, 255, 1, 255, 255, 255, 1, 255, 255, 255, 62, 255, 255, 255, 102, 255, 255, 255, 102, 255, 255, 255, 102, 255, 255, 255, 52, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 1, 255, 255, 255, 60, 255, 255, 255, 102, 255, 255, 255, 102, 255, 255, 255, 102, 255, 255, 255, 63, 255, 255, 255, 63, 255, 255, 255, 102, 255, 255, 255, 102, 255, 255, 255, 102, 255, 255, 255, 60, 255, 255, 255, 1, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 1, 255, 255, 255, 60, 255, 255, 255, 102, 255, 255, 255, 102, 255, 255, 255, 102, 255, 255, 255, 102, 255, 255, 255, 102, 255, 255, 255, 102, 255, 255, 255, 60, 255, 255, 255, 1, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 1, 255, 255, 255, 60, 255, 255, 255, 102, 255, 255, 255, 102, 255, 255, 255, 102, 255, 255, 255, 102, 255, 255, 255, 60, 255, 255, 255, 1, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 1, 255, 255, 255, 60, 255, 255, 255, 102, 255, 255, 255, 102, 255, 255, 255, 60, 255, 255, 255, 1, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 28, 255, 255, 255, 28, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0), +"format": "RGBA8", +"height": 18, +"mipmaps": false, +"width": 18 +} + +[sub_resource type="ImageTexture" id="ImageTexture_fpvt0"] +image = SubResource("Image_wk0kh") + +[node name="ASWizardWindow" type="PanelContainer"] +offset_right = 600.0 +offset_bottom = 600.0 +size_flags_horizontal = 3 +size_flags_vertical = 0 +script = ExtResource("1") + +[node name="container" type="MarginContainer" parent="."] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="options" type="VBoxContainer" parent="container"] +layout_mode = 2 +theme_override_constants/separation = 15 + +[node name="file_location" type="VBoxContainer" parent="container/options"] +layout_mode = 2 + +[node name="file_location_label" type="Label" parent="container/options/file_location"] +layout_mode = 2 +tooltip_text = "Location of the Aseprite *.ase source file" +mouse_filter = 1 +text = "Aseprite File Location:" +clip_text = true + +[node name="HBoxContainer" type="HBoxContainer" parent="container/options/file_location"] +layout_mode = 2 + +[node name="file_location_path" type="LineEdit" parent="container/options/file_location/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "res://test/llama_import.aseprite" +caret_blink = true + +[node name="file_location_btn" type="Button" parent="container/options/file_location/HBoxContainer"] +layout_mode = 2 +text = "Select" + +[node name="layer_section" type="VBoxContainer" parent="container/options"] +layout_mode = 2 + +[node name="header" type="HBoxContainer" parent="container/options/layer_section"] +layout_mode = 2 + +[node name="section_header" type="Button" parent="container/options/layer_section/header"] +layout_mode = 2 +text = "Layers" +icon = SubResource("ImageTexture_fpvt0") +flat = true +alignment = 0 + +[node name="HSeparator" type="HSeparator" parent="container/options/layer_section/header"] +visible = false +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="section_content" type="MarginContainer" parent="container/options/layer_section"] +layout_mode = 2 +theme_override_constants/margin_left = 20 + +[node name="items" type="VBoxContainer" parent="container/options/layer_section/section_content"] +layout_mode = 2 + +[node name="exclude_pattern" type="VBoxContainer" parent="container/options/layer_section/section_content/items"] +layout_mode = 2 + +[node name="label" type="Label" parent="container/options/layer_section/section_content/items/exclude_pattern"] +layout_mode = 2 +tooltip_text = "If layer name matches this pattern, it won't be exported." +mouse_filter = 1 +text = "Exclude layers with name matching pattern:" + +[node name="pattern" type="LineEdit" parent="container/options/layer_section/section_content/items/exclude_pattern"] +layout_mode = 2 +text = "_$" +caret_blink = true + +[node name="split_layers" type="HBoxContainer" parent="container/options/layer_section/section_content/items"] +layout_mode = 2 + +[node name="field" type="CheckBox" parent="container/options/layer_section/section_content/items/split_layers"] +layout_mode = 2 +tooltip_text = "If selected, one resource will be created for each layer. +If not selected, layers will be merged and exported as one SpriteSheet." +button_pressed = true +text = "Split layers in multiple resources" + +[node name="visible_layers" type="HBoxContainer" parent="container/options/layer_section/section_content/items"] +layout_mode = 2 + +[node name="field" type="CheckBox" parent="container/options/layer_section/section_content/items/visible_layers"] +layout_mode = 2 +tooltip_text = "If selected, layers not visible in the source file won't be included in the final image." +text = "Only include visible layers" + +[node name="output_section" type="VBoxContainer" parent="container/options"] +layout_mode = 2 + +[node name="header" type="HBoxContainer" parent="container/options/output_section"] +layout_mode = 2 + +[node name="section_header" type="Button" parent="container/options/output_section/header"] +layout_mode = 2 +text = "Output" +icon = SubResource("ImageTexture_fpvt0") +flat = true +alignment = 0 + +[node name="HSeparator" type="HSeparator" parent="container/options/output_section/header"] +visible = false +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="section_content" type="MarginContainer" parent="container/options/output_section"] +layout_mode = 2 +theme_override_constants/margin_left = 20 + +[node name="items" type="VBoxContainer" parent="container/options/output_section/section_content"] +layout_mode = 2 + +[node name="output_folder" type="VBoxContainer" parent="container/options/output_section/section_content/items"] +layout_mode = 2 + +[node name="label" type="Label" parent="container/options/output_section/section_content/items/output_folder"] +layout_mode = 2 +tooltip_text = "Folder where the SpriteSheet resource is going to be saved" +mouse_filter = 1 +text = "Output Folder:" + +[node name="HBoxContainer" type="HBoxContainer" parent="container/options/output_section/section_content/items/output_folder"] +layout_mode = 2 + +[node name="file_location_path" type="LineEdit" parent="container/options/output_section/section_content/items/output_folder/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "res://output/refactor/split_test" +caret_blink = true + +[node name="output_folder_btn" type="Button" parent="container/options/output_section/section_content/items/output_folder/HBoxContainer"] +layout_mode = 2 +text = "Select" + +[node name="custom_filename" type="VBoxContainer" parent="container/options/output_section/section_content/items"] +layout_mode = 2 + +[node name="label" type="Label" parent="container/options/output_section/section_content/items/custom_filename"] +layout_mode = 2 +tooltip_text = "Output filename. In case layers are not being merged, this is used as file prefix. +If not set, source filename is used." +mouse_filter = 1 +text = "Output File Name / Prefix:" + +[node name="pattern" type="LineEdit" parent="container/options/output_section/section_content/items/custom_filename"] +layout_mode = 2 +caret_blink = true + +[node name="disable_resource_creation" type="HBoxContainer" parent="container/options/output_section/section_content/items"] +layout_mode = 2 + +[node name="field" type="CheckBox" parent="container/options/output_section/section_content/items/disable_resource_creation"] +layout_mode = 2 +tooltip_text = "Does not create SpriteFrames resource. Useful if you are only interested in the .json and .png output from Aseprite." +text = "Do not create resource file" + +[node name="buttons" type="HBoxContainer" parent="container/options"] +layout_mode = 2 +alignment = 2 + +[node name="close" type="Button" parent="container/options/buttons"] +layout_mode = 2 +text = "Close" + +[node name="clear" type="Button" parent="container/options/buttons"] +layout_mode = 2 +text = "Clear" + +[node name="next" type="Button" parent="container/options/buttons"] +layout_mode = 2 +text = "Import" + +[connection signal="button_up" from="container/options/file_location/HBoxContainer/file_location_btn" to="." method="_open_aseprite_file_selection_dialog"] +[connection signal="button_down" from="container/options/layer_section/header/section_header" to="." method="_on_layer_section_header_button_down"] +[connection signal="button_down" from="container/options/output_section/header/section_header" to="." method="_on_output_section_header_button_down"] +[connection signal="button_up" from="container/options/output_section/section_content/items/output_folder/HBoxContainer/output_folder_btn" to="." method="_open_output_folder_selection_dialog"] +[connection signal="button_up" from="container/options/buttons/close" to="." method="_on_close_btn_up"] +[connection signal="button_up" from="container/options/buttons/clear" to="." method="_on_clear_button_up"] +[connection signal="button_up" from="container/options/buttons/next" to="." method="_on_next_btn_up"] diff --git a/addons/AsepriteWizard/interface/docks/wizard/imported_sprite_frames.gd b/addons/AsepriteWizard/interface/docks/wizard/imported_sprite_frames.gd new file mode 100644 index 0000000..30bc9c2 --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/imported_sprite_frames.gd @@ -0,0 +1,304 @@ +@tool +extends PanelContainer + +signal import_success(fields) + +const result_code = preload("../../../config/result_codes.gd") +const wizard_config = preload("../../../config/wizard_config.gd") +var _import_helper = preload("./wizard_import_helper.gd").new() + +@onready var _tree_container = $MarginContainer/HSplitContainer/tree +@onready var _resource_tree = _tree_container.get_resource_tree() +@onready var _nothing_container = $MarginContainer/HSplitContainer/MarginContainer/VBoxContainer/nothing +@onready var _single_item_container = $MarginContainer/HSplitContainer/MarginContainer/VBoxContainer/single_item +@onready var _multiple_items_container = $MarginContainer/HSplitContainer/MarginContainer/VBoxContainer/multiple_items +@onready var _confirmation_warning_container = $MarginContainer/HSplitContainer/MarginContainer/VBoxContainer/confirmation_warning + +var _selection_count = 0 + +var _current_container = null +var _resources_to_process = null + +var _is_loaded = false + +var _groups = {} + +func init_resources(): + if _is_loaded: + return + _is_loaded = true + var file_tree = _get_file_tree("res://") + _setup_tree(file_tree) + + +func _get_file_tree(base_path: String, dir_name: String = "") -> Dictionary: + var dir_path = base_path.path_join(dir_name) + var dir = DirAccess.open(dir_path) + var dir_data = { "path": dir_path, "name": dir_name, "children": [], "type": "dir", } + if not dir: + return dir_data + + dir.list_dir_begin() + var file_name = dir.get_next() + + while file_name != "": + if dir.current_is_dir() and _is_importable_folder(dir_path, file_name): + var child_data = _get_file_tree(dir_path, file_name) + if not child_data.children.is_empty(): + dir_data.children.push_back(child_data) + elif file_name.ends_with(".res"): + var resource_path = dir_path.path_join(file_name) + var resource = ResourceLoader.load(resource_path) + if resource is SpriteFrames: + if resource.has_meta(wizard_config.WIZARD_CONFIG_META_NAME): + var meta = resource.get_meta(wizard_config.WIZARD_CONFIG_META_NAME) + var parent_node = dir_data + + if meta.group != "": + var group + if _groups.has(meta.group): + group = _groups[meta.group] + else: + group = { "folders": {} } + _groups[meta.group] = group + + if not group.folders.has(dir_path): + group.folders[dir_path] = { + "node": { + "type": "group", + "name": "Split: %s" % meta.fields.source_file.get_file(), + "has_moved": meta.fields.output_location != dir_path, + "path": meta.fields.source_file, + "folder": dir_path, + "has_changes": _has_source_changes(resource, meta), + "children": [], + } + } + parent_node.children.push_back(group.folders[dir_path].node) + + parent_node = group.folders[dir_path].node + + parent_node.children.push_back({ + "type": "resource", + "resource_type": "SpriteFrames", + "name": resource.resource_path.get_file(), + "path": resource.resource_path, + "meta": meta, + "has_changes": _has_source_changes(resource, meta), + "has_moved": meta.fields.output_location != dir_path, + }) + + file_name = dir.get_next() + + return dir_data + + +func _is_importable_folder(dir_path: String, dir_name: String) -> bool: + return dir_path != "res://" or dir_name != "addons" + + +func _setup_tree(resource_tree: Dictionary) -> void: + var root = _resource_tree.create_item() + _add_items_to_tree(root, resource_tree.children) + + +func _add_items_to_tree(root: TreeItem, children: Array): + for node in children: + var item: TreeItem = _resource_tree.create_item(root) + item.set_text(0, node.name) + item.set_meta("node", node) + match node.type: + "dir": + item.set_icon(0, get_theme_icon("Folder", "EditorIcons")) + _add_items_to_tree(item, node.children) +# + "group": + item.set_icon(0, get_theme_icon("CompressedTexture2D", "EditorIcons")) + _add_items_to_tree(item, node.children) + if node.has_changes: + item.set_text(0, "%s (*)" % node.name) + if node.has_moved: + item.set_suffix(0, "(moved)") + "resource": + item.set_icon(0, get_theme_icon(node.resource_type, "EditorIcons")) + if node.has_changes: + item.set_text(0, "%s (*)" % node.name) + if node.meta.group != "": + item.set_custom_color(0, item.get_icon_modulate(0).darkened(0.5)) + item.set_selectable(0, false) + elif node.has_moved: + item.set_suffix(0, "(moved)") + + +func _has_source_changes(resource: Object, meta: Dictionary) -> bool: + var current_hash = FileAccess.get_md5(meta.fields.source_file) + var saved_hash = wizard_config.get_source_hash(resource) + + return saved_hash != current_hash + + +func _is_supported_type(resource_type: String) -> bool: + return resource_type == "SpriteFrames" + + +func reload_tree(): + _confirmation_warning_container.hide() + _resources_to_process = null + if _current_container != null: + _current_container.show_buttons() + _current_container = null + + _groups = {} + _selection_count = 0 + _resource_tree.clear() + var file_tree = _get_file_tree("res://") + _setup_tree(file_tree) + + +func _set_empty_details_state(): + _nothing_container.show() + _single_item_container.hide() + _multiple_items_container.hide() + + +func _on_tree_multi_selected(item: TreeItem, column: int, selected: bool): + _confirmation_warning_container.hide() + _resources_to_process = null + if _current_container != null: + _current_container.show_buttons() +# + if selected: + _selection_count += 1 + else: + _selection_count -= 1 +# + _nothing_container.hide() + _single_item_container.hide() + _multiple_items_container.hide() +# + match _selection_count: + 0: + _nothing_container.show() + 1: + _single_item_container.show() + _set_item_details(_resource_tree.get_selected()) + _current_container = _single_item_container + _: + _multiple_items_container.show() + _multiple_items_container.set_selected_count(_selection_count) + _current_container = _multiple_items_container + + +func _set_item_details(item: TreeItem) -> void: + if not item.has_meta("node"): + return + var data = item.get_meta("node") + _single_item_container.set_resource_details(data) + + +func _on_single_item_import_triggered(): + var selected = _resource_tree.get_selected() + var meta = selected.get_meta("node") + + match meta.type: + "dir": + var selected_item = _resource_tree.get_selected() + var all_resources = [] + var scenes_to_open = _set_all_resources(selected_item.get_meta("node"), all_resources) + _resources_to_process = all_resources + _show_confirmation_message(all_resources.size()) + "resource": + var code = await _do_import(meta.path, meta.meta) + _set_tree_item_as_saved(_resource_tree.get_selected()) + _single_item_container.hide_source_change_warning() + if code == OK: + EditorInterface.get_resource_filesystem().scan() + "group": + var first_item = meta.children[0] + var code = await _do_import(first_item.path, first_item.meta) + _set_tree_item_as_saved(selected) + _single_item_container.hide_source_change_warning() + if code == OK: + EditorInterface.get_resource_filesystem().scan() + _on_tree_refresh_triggered() + + +func _on_confirmation_warning_warning_confirmed(): + _confirmation_warning_container.hide() + _current_container.show_buttons() + + for resource in _resources_to_process: + await _do_import(resource.path, resource.meta) + + _resources_to_process = null + EditorInterface.get_resource_filesystem().scan() + _on_tree_refresh_triggered() + + +func _on_confirmation_warning_warning_declined(): + _confirmation_warning_container.hide() + _current_container.show_buttons() + _resources_to_process = null + + +func _on_tree_refresh_triggered(): + _set_empty_details_state() + reload_tree() + + +func _set_tree_item_as_saved(item: TreeItem) -> void: + var meta = item.get_meta("node") + meta.has_changes = false + meta.has_moved = false + item.set_meta("node", meta) + item.set_text(0, meta.name) + item.set_suffix(0, "") + + +func _do_import(resource_path: String, metadata: Dictionary) -> int: + var resource_base_dir = resource_path.get_base_dir() + + if resource_base_dir != metadata.fields.output_location: + print("Resource has moved. Changing output folder from %s to %s" % [resource_base_dir, metadata.fields.output_location]) + metadata.fields.output_location = resource_base_dir + + var exit_code := await _import_helper.import_and_create_resources(metadata.fields.source_file, metadata.fields) + + if exit_code == OK: + print("Import complete: %s" % resource_path) + import_success.emit(metadata.fields) + else: + printerr("Failed to import %s. Error: %s" % [resource_path, result_code.get_error_message(exit_code)]) + + return exit_code + + +func _set_all_resources(meta: Dictionary, resources: Array): + match meta.type: + "dir": + for c in meta.children: + _set_all_resources(c, resources) + "resource": + if not resources.has(meta): + resources.push_back(meta) + "group": + var first_item = meta.children[0] + resources.push_back(first_item) + + +func _show_confirmation_message(resources: int): + _current_container.hide_buttons() + _confirmation_warning_container.set_message("You are about to re-import %s resources. Do you wish to continue?" % resources) + _confirmation_warning_container.show() + + +func _on_multiple_items_import_triggered(): + var selected_item = _resource_tree.get_next_selected(null) + var all_resources = [] + + while selected_item != null: + _set_all_resources(selected_item.get_meta("node"), all_resources) + selected_item = _resource_tree.get_next_selected(selected_item) + + _resources_to_process = all_resources + _show_confirmation_message(all_resources.size()) diff --git a/addons/AsepriteWizard/interface/docks/wizard/imported_sprite_frames.tscn b/addons/AsepriteWizard/interface/docks/wizard/imported_sprite_frames.tscn new file mode 100644 index 0000000..ca76e18 --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/imported_sprite_frames.tscn @@ -0,0 +1,67 @@ +[gd_scene load_steps=6 format=3 uid="uid://bfhsdslj8kt7b"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/imported_sprite_frames.gd" id="1_dk2ra"] +[ext_resource type="PackedScene" uid="uid://cisgsfvp4nf1g" path="res://addons/AsepriteWizard/interface/shared/tree/resource_tree.tscn" id="2_svrqo"] +[ext_resource type="PackedScene" uid="uid://q7eyyg2kvvv2" path="res://addons/AsepriteWizard/interface/docks/wizard/resource_tree_single_item.tscn" id="3_pe1cg"] +[ext_resource type="PackedScene" uid="uid://fscemkx5w1dw" path="res://addons/AsepriteWizard/interface/docks/wizard/resource_tree_multiple_items.tscn" id="4_vteew"] +[ext_resource type="PackedScene" uid="uid://qgmln507kjnm" path="res://addons/AsepriteWizard/interface/shared/tree/tree_selection_confirmation_warning.tscn" id="5_jigim"] + +[node name="ImportedSpriteFrames" type="PanelContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_dk2ra") + +[node name="MarginContainer" type="MarginContainer" parent="."] +layout_mode = 2 + +[node name="HSplitContainer" type="HSplitContainer" parent="MarginContainer"] +layout_mode = 2 + +[node name="tree" parent="MarginContainer/HSplitContainer" instance=ExtResource("2_svrqo")] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/HSplitContainer"] +custom_minimum_size = Vector2(200, 0) +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/HSplitContainer/MarginContainer"] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer/MarginContainer"] +layout_mode = 2 +text = "Details" +horizontal_alignment = 1 + +[node name="HSeparator" type="HSeparator" parent="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="nothing" type="Label" parent="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 6 +text = "Nothing selected" +horizontal_alignment = 1 + +[node name="single_item" parent="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer" instance=ExtResource("3_pe1cg")] +visible = false +layout_mode = 2 + +[node name="multiple_items" parent="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer" instance=ExtResource("4_vteew")] +visible = false +layout_mode = 2 + +[node name="confirmation_warning" parent="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer" instance=ExtResource("5_jigim")] +visible = false +layout_mode = 2 + +[connection signal="multi_selected" from="MarginContainer/HSplitContainer/tree" to="." method="_on_tree_multi_selected"] +[connection signal="refresh_triggered" from="MarginContainer/HSplitContainer/tree" to="." method="_on_tree_refresh_triggered"] +[connection signal="import_triggered" from="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer/single_item" to="." method="_on_single_item_import_triggered"] +[connection signal="import_triggered" from="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer/multiple_items" to="." method="_on_multiple_items_import_triggered"] +[connection signal="warning_confirmed" from="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer/confirmation_warning" to="." method="_on_confirmation_warning_warning_confirmed"] +[connection signal="warning_declined" from="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer/confirmation_warning" to="." method="_on_confirmation_warning_warning_declined"] diff --git a/addons/AsepriteWizard/interface/docks/wizard/resource_tree_multiple_items.gd b/addons/AsepriteWizard/interface/docks/wizard/resource_tree_multiple_items.gd new file mode 100644 index 0000000..cf8882b --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/resource_tree_multiple_items.gd @@ -0,0 +1,22 @@ +@tool +extends VBoxContainer + +signal import_triggered + +@onready var _import_message = $message +@onready var _import_button = $buttons + +func set_selected_count(number_of_items: int) -> void: + _import_message.text = "%2d items selected" % number_of_items + + +func show_buttons(): + _import_button.show() + + +func hide_buttons(): + _import_button.hide() + + +func _on_import_selected_button_up(): + import_triggered.emit() diff --git a/addons/AsepriteWizard/interface/docks/wizard/resource_tree_multiple_items.tscn b/addons/AsepriteWizard/interface/docks/wizard/resource_tree_multiple_items.tscn new file mode 100644 index 0000000..7e14c48 --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/resource_tree_multiple_items.tscn @@ -0,0 +1,24 @@ +[gd_scene load_steps=2 format=3 uid="uid://fscemkx5w1dw"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/resource_tree_multiple_items.gd" id="1_lg81l"] + +[node name="multiple_items" type="VBoxContainer"] +size_flags_vertical = 3 +script = ExtResource("1_lg81l") + +[node name="message" type="Label" parent="."] +layout_mode = 2 +size_flags_vertical = 6 +text = "Multiple items selected +" +horizontal_alignment = 1 + +[node name="buttons" type="HFlowContainer" parent="."] +layout_mode = 2 + +[node name="import_selected" type="Button" parent="buttons"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Re-Import all" + +[connection signal="button_up" from="buttons/import_selected" to="." method="_on_import_selected_button_up"] diff --git a/addons/AsepriteWizard/interface/docks/wizard/resource_tree_single_item.gd b/addons/AsepriteWizard/interface/docks/wizard/resource_tree_single_item.gd new file mode 100644 index 0000000..76c5b86 --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/resource_tree_single_item.gd @@ -0,0 +1,171 @@ +@tool +extends VBoxContainer + +signal import_triggered + +@onready var _file_name = $GridContainer/file_name_value +@onready var _type = $GridContainer/type_value +@onready var _path = $GridContainer/path_value + +@onready var _source_label = $GridContainer/source_file_label +@onready var _source = $GridContainer/source_file_value +@onready var _only_visible_layers_label = $GridContainer/only_visible_layers_label +@onready var _only_visible_layers = $GridContainer/only_visible_layers_value +@onready var _layer_ex_pattern_label = $GridContainer/layer_ex_patt_label +@onready var _layer_ex_pattern = $GridContainer/layer_ex_patt_value + +@onready var _o_name_label = $GridContainer/o_name_label +@onready var _o_name = $GridContainer/o_name_value +@onready var _o_folder_label = $GridContainer/o_folder_label +@onready var _o_folder_value = $GridContainer/o_folder_value + +@onready var _resource_list_label = $GridContainer/resource_list_label +@onready var _resource_list = $GridContainer/resource_list +@onready var _resource_list_separator_1 = $GridContainer/HSeparator3 +@onready var _resource_list_separator_2 = $GridContainer/HSeparator4 + +@onready var _resource_buttons = $resource_buttons +@onready var _dir_buttons = $dir_buttons +@onready var _group_buttons = $group_buttons + +@onready var _source_change_warning = $source_changed_warning + +@onready var _resource_only_fields = [ + _source_label, + _source, + _only_visible_layers_label, + _only_visible_layers, + _layer_ex_pattern_label, + _layer_ex_pattern, + _o_name_label, + _o_name, + _o_folder_label, + _o_folder_value, + _source_change_warning, +] + +var _current_resource_type = "resource" +var _resource_config: Dictionary = {} + +func _ready(): + _source_change_warning.set_text("Source file changed since last import") + _source_change_warning.hide() + + +func set_resource_details(resource_details: Dictionary) -> void: + _resource_config = resource_details + _resource_buttons.hide() + _dir_buttons.hide() + _group_buttons.hide() + _hide_resource_list() + + _source_change_warning.hide() + + _file_name.text = resource_details.name + _path.text = resource_details.path + + _current_resource_type = resource_details.type + match resource_details.type: + "resource": + _type.text = resource_details.resource_type + _show_resource_fields() + _resource_buttons.show() + + var fields = resource_details.meta.fields + _load_fields(fields) + _resource_buttons.show() + _source_change_warning.visible = resource_details.has_changes + "group": + _type.text = "Split Group" + _show_resource_fields() + _load_fields(resource_details.children[0].meta.fields) + _source_change_warning.visible = resource_details.children[0].has_changes + _group_buttons.show() + _show_resource_list() + for c in _resource_list.get_children(): + c.queue_free() + + for child_resource in resource_details.children: + var label = Label.new() + label.text = child_resource.name + _resource_list.add_child(label) + _: + _type.text = "Folder" + _hide_resource_fields() + _dir_buttons.show() + return + + +func _load_fields(fields: Dictionary): + _only_visible_layers.text = "Yes" if fields.only_visible_layers else "No" + _layer_ex_pattern.text = fields.layer_exclusion_pattern + + _o_name.text = fields.output_name + _o_folder_value.text = fields.output_location + + _source.text = fields.source_file + + +func _hide_resource_fields(): + for f in _resource_only_fields: + f.hide() + + +func _show_resource_fields(): + for f in _resource_only_fields: + f.show() + + +func show_buttons(): + match _current_resource_type: + "resource": + _resource_buttons.show() + _: + _dir_buttons.show() + + +func hide_buttons(): + _resource_buttons.hide() + _dir_buttons.hide() + + +func hide_source_change_warning(): + _source_change_warning.hide() + + +func _on_show_in_fs_button_up(): + EditorInterface.get_file_system_dock().navigate_to_path(_path.text) + + +func _on_show_dir_in_fs_button_up(): + EditorInterface.get_file_system_dock().navigate_to_path(_path.text) + + +func _on_import_all_button_up(): + import_triggered.emit() + + +func _on_import_button_up(): + import_triggered.emit() + + +func _hide_resource_list(): + _resource_list_separator_1.hide() + _resource_list_separator_2.hide() + _resource_list_label.hide() + _resource_list.hide() + + +func _show_resource_list(): + _resource_list_separator_1.show() + _resource_list_separator_2.show() + _resource_list_label.show() + _resource_list.show() + + +func _on_import_all_pressed(): + import_triggered.emit() + + +func _on_show_in_fs_pressed(): + EditorInterface.get_file_system_dock().navigate_to_path(_resource_config.children[0].path) diff --git a/addons/AsepriteWizard/interface/docks/wizard/resource_tree_single_item.tscn b/addons/AsepriteWizard/interface/docks/wizard/resource_tree_single_item.tscn new file mode 100644 index 0000000..9603625 --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/resource_tree_single_item.tscn @@ -0,0 +1,172 @@ +[gd_scene load_steps=3 format=3 uid="uid://q7eyyg2kvvv2"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/resource_tree_single_item.gd" id="1_h1q4t"] +[ext_resource type="PackedScene" uid="uid://c1l0bk12iwln3" path="res://addons/AsepriteWizard/interface/shared/tree/inline_warning_panel.tscn" id="2_hmv61"] + +[node name="single_item" type="VBoxContainer"] +script = ExtResource("1_h1q4t") + +[node name="GridContainer" type="GridContainer" parent="."] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_constants/h_separation = 10 +columns = 2 + +[node name="type_label" type="Label" parent="GridContainer"] +layout_mode = 2 +size_flags_vertical = 0 +text = "Type" + +[node name="type_value" type="Label" parent="GridContainer"] +layout_mode = 2 +text = "-" + +[node name="file_name_label" type="Label" parent="GridContainer"] +layout_mode = 2 +size_flags_vertical = 0 +text = "File" + +[node name="file_name_value" type="Label" parent="GridContainer"] +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +text = "-" +autowrap_mode = 1 + +[node name="path_label" type="Label" parent="GridContainer"] +layout_mode = 2 +size_flags_vertical = 0 +text = "Path" + +[node name="path_value" type="Label" parent="GridContainer"] +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +size_flags_horizontal = 3 +text = "-" +autowrap_mode = 1 + +[node name="HSeparator" type="HSeparator" parent="GridContainer"] +layout_mode = 2 + +[node name="HSeparator2" type="HSeparator" parent="GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="source_file_label" type="Label" parent="GridContainer"] +layout_mode = 2 +size_flags_vertical = 0 +text = "Aseprite File" + +[node name="source_file_value" type="Label" parent="GridContainer"] +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +size_flags_horizontal = 3 +text = "-" +autowrap_mode = 1 + +[node name="only_visible_layers_label" type="Label" parent="GridContainer"] +layout_mode = 2 +size_flags_vertical = 0 +text = "Only Visible Layers" + +[node name="only_visible_layers_value" type="Label" parent="GridContainer"] +layout_mode = 2 +text = "-" + +[node name="layer_ex_patt_label" type="Label" parent="GridContainer"] +layout_mode = 2 +size_flags_vertical = 0 +text = "Slice" + +[node name="layer_ex_patt_value" type="Label" parent="GridContainer"] +layout_mode = 2 +text = "-" + +[node name="o_folder_label" type="Label" parent="GridContainer"] +visible = false +layout_mode = 2 +size_flags_vertical = 0 +text = "Output Folder" + +[node name="o_folder_value" type="Label" parent="GridContainer"] +visible = false +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +size_flags_horizontal = 3 +text = "-" +autowrap_mode = 2 + +[node name="o_name_label" type="Label" parent="GridContainer"] +layout_mode = 2 +size_flags_vertical = 0 +text = "Output Name" + +[node name="o_name_value" type="Label" parent="GridContainer"] +layout_mode = 2 +text = "-" + +[node name="HSeparator3" type="HSeparator" parent="GridContainer"] +layout_mode = 2 + +[node name="HSeparator4" type="HSeparator" parent="GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="resource_list_label" type="Label" parent="GridContainer"] +layout_mode = 2 +size_flags_vertical = 0 +text = "Resources" + +[node name="resource_list" type="VBoxContainer" parent="GridContainer"] +layout_mode = 2 + +[node name="source_changed_warning" parent="." instance=ExtResource("2_hmv61")] +layout_mode = 2 + +[node name="resource_buttons" type="HFlowContainer" parent="."] +visible = false +layout_mode = 2 + +[node name="import" type="Button" parent="resource_buttons"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Re-Import" + +[node name="show_in_fs" type="Button" parent="resource_buttons"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Show In FileSystem" + +[node name="group_buttons" type="HFlowContainer" parent="."] +visible = false +layout_mode = 2 + +[node name="import_all" type="Button" parent="group_buttons"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Re-Import all" + +[node name="show_in_fs" type="Button" parent="group_buttons"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Show In FileSystem" + +[node name="dir_buttons" type="HFlowContainer" parent="."] +visible = false +layout_mode = 2 + +[node name="import_all" type="Button" parent="dir_buttons"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Re-Import all" + +[node name="show_dir_in_fs" type="Button" parent="dir_buttons"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Show In FileSystem" + +[connection signal="button_up" from="resource_buttons/import" to="." method="_on_import_button_up"] +[connection signal="button_up" from="resource_buttons/show_in_fs" to="." method="_on_show_in_fs_button_up"] +[connection signal="pressed" from="group_buttons/import_all" to="." method="_on_import_all_pressed"] +[connection signal="pressed" from="group_buttons/show_in_fs" to="." method="_on_show_in_fs_pressed"] +[connection signal="button_up" from="dir_buttons/import_all" to="." method="_on_import_all_button_up"] +[connection signal="button_up" from="dir_buttons/show_dir_in_fs" to="." method="_on_show_dir_in_fs_button_up"] diff --git a/addons/AsepriteWizard/interface/docks/wizard/sprite_frames_import_history.gd b/addons/AsepriteWizard/interface/docks/wizard/sprite_frames_import_history.gd new file mode 100644 index 0000000..d7f2558 --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/sprite_frames_import_history.gd @@ -0,0 +1,260 @@ +@tool +extends PanelContainer + +signal request_edit(import_cfg) +signal request_import(import_cfg) + +const SourcePathField = preload("./wizard_nodes/source_path.tscn") +const OutputPathField = preload("./wizard_nodes/output_path.tscn") +const ImportDateField = preload("./wizard_nodes/import_date.tscn") +const ItemActions = preload("./wizard_nodes/list_actions.tscn") +const DetailsField = preload("./wizard_nodes/details.tscn") + +const SORT_BY_DATE := 0 +const SORT_BY_PATH := 1 +const INITIAL_GRID_INDEX := 4 + +var _config = preload("../../../config/config.gd").new() +var _history: Array +var _history_nodes := {} +var _history_nodes_list := [] +var _is_busy := false +var _import_requested_for := -1 +var _sort_by = SORT_BY_DATE + +@onready var grid = $MarginContainer/VBoxContainer/ScrollContainer/GridContainer +@onready var loading_warning = $MarginContainer/VBoxContainer/loading_warning +@onready var no_history_warning = $MarginContainer/VBoxContainer/no_history_warning + + +func reload(): + if _history: + return + + if _config.has_old_history(): + _migrate_history() + + _history = _config.get_import_history() + + for index in range(_history.size()): + var entry = _history[index] + _create_node_list_entry(entry, index) + + loading_warning.hide() + if _history.is_empty(): + no_history_warning.show() + else: + grid.get_parent().show() + + +func _create_node_list_entry(entry: Dictionary, index: int): + _add_to_node_list(entry, _create_nodes(entry, index)) + + +func _create_nodes(entry: Dictionary, index: int) -> Dictionary: + var import_date = ImportDateField.instantiate() + import_date.set_date(entry.import_date) + + var source_path = SourcePathField.instantiate() + source_path.set_entry(entry) + + var output_path = OutputPathField.instantiate() + output_path.text = entry.output_location + output_path.tooltip_text = entry.output_location + + var details = DetailsField.instantiate() + details.set_details(entry) + + var actions = ItemActions.instantiate() + actions.history_index = index + actions.connect("import_clicked",Callable(self,"_on_entry_reimport_clicked")) + actions.connect("edit_clicked",Callable(self,"_on_entry_edit_clicked")) + actions.connect("removed_clicked",Callable(self,"_on_entry_remove_clicked")) + + + grid.get_child(INITIAL_GRID_INDEX).add_sibling(import_date) + import_date.add_sibling(source_path) + source_path.add_sibling(output_path) + output_path.add_sibling(details) + details.add_sibling(actions) + + return { + "history_index": index, + "timestamp": entry.import_date, + "source_file": entry.source_file, + "source_path_node": source_path, + "output_path_node": output_path, + "import_date_node": import_date, + "actions_node": actions, + "details_node": details, + } + + +func _add_to_node_list(entry: Dictionary, node: Dictionary): + if not _history_nodes.has(entry.source_file): + _history_nodes[entry.source_file] = [] + _history_nodes[entry.source_file].push_front(node) + _history_nodes_list.push_front(node) + + +func add_entry(file_settings: Dictionary): + if _history == null: + reload() +# + var dt = Time.get_datetime_dict_from_system() + file_settings["import_date"] = "%04d-%02d-%02d %02d:%02d:%02d" % [dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second] +# + if _import_requested_for != -1: + _remove_item(_import_requested_for) + _import_requested_for = -1 + elif _history.size() > _config.get_history_max_entries(): + _remove_entries(_history[0].source_file, 0) +# + _history.push_back(file_settings) + _config.save_import_history(_history) + _create_node_list_entry(file_settings, _history.size() - 1) + + if _sort_by == SORT_BY_PATH: + _trigger_sort() + + no_history_warning.hide() + loading_warning.hide() + grid.get_parent().show() + _is_busy = false + + +func _on_entry_reimport_clicked(entry_index: int): + if _is_busy: + return + _is_busy = true + _import_requested_for = entry_index + emit_signal("request_import", _history[entry_index]) + + +func _on_entry_edit_clicked(entry_index: int): + if _is_busy: + return + emit_signal("request_edit", _history[entry_index]) + + +func _on_entry_remove_clicked(entry_index: int): + if _is_busy: + return + _is_busy = true + + _remove_item(entry_index) + _config.save_import_history(_history) + + if (_history.is_empty()): + grid.get_parent().hide() + no_history_warning.show() + + _is_busy = false + + +func _remove_item(entry_index: int): + var entry = _history[entry_index] + _remove_entries(entry.source_file, entry_index) + + +# removes nodes and entry from history. If entry_index is not provided, all +# entries for path are removed. +func _remove_entries(source_file_path: String, entry_index: int = -1): + var files_entries = _history_nodes[source_file_path] + var indexes_to_remove = [] + + for f in files_entries: + if entry_index == -1 or f.history_index == entry_index: + _free_entry_nodes(f) + _history_nodes_list.erase(f) + + if entry_index != -1: + files_entries.erase(f) + _remove_from_history(f.history_index) + return + + indexes_to_remove.push_back(f.history_index) + + for i in indexes_to_remove: + _remove_from_history(i) + + _history_nodes[source_file_path] = [] + + +func _remove_from_history(entry_index: int): + var _already_adjusted = [] + # to avoid re-creating the whole nodes list, I just decrement + # the index from items newer than the excluded one + for index in range(entry_index + 1, _history.size()): + if _already_adjusted.has(_history[index].source_file): + continue + _already_adjusted.push_back(_history[index].source_file) + var nodes = _history_nodes[_history[index].source_file] + for f in nodes: + if f.history_index > entry_index: + f.history_index -= 1 + f.actions_node.history_index = f.history_index + + _history.remove_at(entry_index) + + +func _free_entry_nodes(entry_history_node: Dictionary): + entry_history_node.source_path_node.queue_free() + entry_history_node.output_path_node.queue_free() + entry_history_node.import_date_node.queue_free() + entry_history_node.actions_node.queue_free() + entry_history_node.details_node.queue_free() + + +func _on_SortOptions_item_selected(index): + if index == _sort_by: + return + + _trigger_sort(index) + + +func _trigger_sort(sort_type: int = _sort_by): + if sort_type == SORT_BY_DATE: + _history_nodes_list.sort_custom(Callable(self,"_sort_by_date")) + else: + _history_nodes_list.sort_custom(Callable(self,"_sort_by_path")) + _reorganise_nodes() + _sort_by = sort_type + + +func _sort_by_date(a, b): + return a.timestamp < b.timestamp + + +func _sort_by_path(a, b): + return a.source_file > b.source_file + + +func _reorganise_nodes(): + for entry in _history_nodes_list: + grid.move_child(entry.import_date_node, INITIAL_GRID_INDEX + 1) + grid.move_child(entry.source_path_node, INITIAL_GRID_INDEX + 2) + grid.move_child(entry.output_path_node, INITIAL_GRID_INDEX + 3) + grid.move_child(entry.details_node, INITIAL_GRID_INDEX + 4) + grid.move_child(entry.actions_node, INITIAL_GRID_INDEX + 5) + + +func _migrate_history(): + var history = _config.get_old_import_history() + var new_history = [] + + for index in range(history.size()): + var entry = history[index] + new_history.push_back({ + "split_layers": true if entry.options.export_mode else false, + "only_visible_layers": entry.options.only_visible_layers, + "layer_exclusion_pattern": entry.options.exception_pattern, + "output_name": entry.options.output_filename, + "source_file": entry.source_file, + "do_not_create_resource": entry.options.do_not_create_resource, + "output_location": entry.output_location, + "import_date": entry.import_date, + }) + + _config.save_import_history(new_history) + _config.remove_old_history_setting() diff --git a/addons/AsepriteWizard/interface/docks/wizard/sprite_frames_import_history.tscn b/addons/AsepriteWizard/interface/docks/wizard/sprite_frames_import_history.tscn new file mode 100644 index 0000000..f4f93e5 --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/sprite_frames_import_history.tscn @@ -0,0 +1,85 @@ +[gd_scene load_steps=2 format=3 uid="uid://cyoin5ncul0fm"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/sprite_frames_import_history.gd" id="1"] + +[node name="SpriteFramesImportHistory" type="PanelContainer"] +anchors_preset = 10 +anchor_right = 1.0 +grow_horizontal = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +script = ExtResource("1") + +[node name="MarginContainer" type="MarginContainer" parent="."] +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"] +layout_mode = 2 + +[node name="list actions" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 0 +alignment = 2 + +[node name="divider" type="Label" parent="MarginContainer/VBoxContainer/list actions"] +layout_mode = 2 + +[node name="sort_label" type="Label" parent="MarginContainer/VBoxContainer/list actions"] +layout_mode = 2 +text = "Sort by:" + +[node name="SortOptions" type="OptionButton" parent="MarginContainer/VBoxContainer/list actions"] +layout_mode = 2 +item_count = 2 +selected = 0 +popup/item_0/text = "Date" +popup/item_0/id = 0 +popup/item_1/text = "Source File" +popup/item_1/id = 1 + +[node name="loading_warning" type="Label" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 6 +text = "Loading..." + +[node name="no_history_warning" type="Label" parent="MarginContainer/VBoxContainer"] +visible = false +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 6 +text = "No import history" + +[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer/VBoxContainer"] +visible = false +layout_mode = 2 +size_flags_vertical = 3 + +[node name="GridContainer" type="GridContainer" parent="MarginContainer/VBoxContainer/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +columns = 5 + +[node name="date_label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/GridContainer"] +layout_mode = 2 +text = "Date" + +[node name="source_label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "Source File" + +[node name="output_label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/GridContainer"] +layout_mode = 2 +text = "Output Folder" + +[node name="details_label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/GridContainer"] +layout_mode = 2 +text = "Details" + +[node name="actions_label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/GridContainer"] +layout_mode = 2 +text = "Actions" + +[connection signal="item_selected" from="MarginContainer/VBoxContainer/list actions/SortOptions" to="." method="_on_SortOptions_item_selected"] diff --git a/addons/AsepriteWizard/interface/docks/wizard/wizard_import_helper.gd b/addons/AsepriteWizard/interface/docks/wizard/wizard_import_helper.gd new file mode 100644 index 0000000..29bac77 --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/wizard_import_helper.gd @@ -0,0 +1,69 @@ +@tool +extends Node + +const wizard_meta = preload("../../../config/wizard_config.gd") +const result_code = preload("../../../config/result_codes.gd") +var _aseprite_file_exporter = preload("../../../aseprite/file_exporter.gd").new() +var _sf_creator = preload("../../../creators/sprite_frames/sprite_frames_creator.gd").new() +var _config = preload("../../../config/config.gd").new() + +var _file_system: EditorFileSystem = EditorInterface.get_resource_filesystem() + + +# fields +# "split_layers" +# "only_visible_layers" +# "layer_exclusion_pattern" +# "output_name" +# "source_file" +# "do_not_create_resource" +# "output_location" +func import_and_create_resources(aseprite_file: String, fields: Dictionary) -> int: + var export_mode = _aseprite_file_exporter.LAYERS_EXPORT_MODE if fields.split_layers else _aseprite_file_exporter.FILE_EXPORT_MODE + var options = { + "export_mode": export_mode, + "exception_pattern": fields.layer_exclusion_pattern, + "only_visible_layers": fields.only_visible_layers, + "output_filename": fields.output_name, + "do_not_create_resource": fields.do_not_create_resource, + "output_folder": fields.output_location, + } + + var aseprite_output = _aseprite_file_exporter.generate_aseprite_files( + ProjectSettings.globalize_path(aseprite_file), + options + ) + + if not aseprite_output.is_ok: + return aseprite_output.code + + _file_system.scan() + + await _file_system.filesystem_changed + + var exit_code = OK + + if !options.get("do_not_create_resource", false): + var resources = _sf_creator.create_resources(aseprite_output.content) + if resources.is_ok: + _add_metadata(resources.content, aseprite_file, fields, options) + exit_code = _sf_creator.save_resources(resources.content) + + if _config.should_remove_source_files(): + _remove_source_files(aseprite_output.content) + + return exit_code + + +func _add_metadata(resources: Array, aseprite_file: String, fields: Dictionary, options: Dictionary) -> void: + var source_hash = FileAccess.get_md5(aseprite_file) + var group = str(ResourceUID.create_id()) if options.export_mode == _aseprite_file_exporter.LAYERS_EXPORT_MODE else "" + + for r in resources: + wizard_meta.set_source_hash(r.resource, source_hash) + wizard_meta.save_config(r.resource, { "fields": fields, "group": group }) + + +func _remove_source_files(source_files: Array): + for s in source_files: + DirAccess.remove_absolute(s.data_file) diff --git a/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/details.gd b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/details.gd new file mode 100644 index 0000000..bed082f --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/details.gd @@ -0,0 +1,42 @@ +@tool +extends VBoxContainer + +@onready var _details_btn = $label +@onready var _details_container = $MarginContainer/GridContainer + +@onready var _split_layers_field = $MarginContainer/GridContainer/split_layers +@onready var _only_visible_layers = $MarginContainer/GridContainer/only_visible_layers +@onready var _layer_exclusion_pattern = $MarginContainer/GridContainer/layer_exclusion_pattern +@onready var _output_name = $MarginContainer/GridContainer/output_name +@onready var _do_not_create_resource = $MarginContainer/GridContainer/do_not_create_resource + +var _entry + +func _ready(): + _adjust_icon(false) + _details_container.hide() + _load_fields() + + +func set_details(entry: Dictionary): + _entry = entry + + +func _load_fields(): + _split_layers_field.text = "Yes" if _entry.split_layers else "No" + _only_visible_layers.text = "Yes" if _entry.only_visible_layers else "No" + _layer_exclusion_pattern.text = _entry.layer_exclusion_pattern + _output_name.text = _entry.output_name + _output_name.text = _entry.output_name + _do_not_create_resource.text = "Yes" if _entry.do_not_create_resource else "No" + + +func _adjust_icon(is_visible: bool) -> void: + var icon_name = "GuiTreeArrowDown" if is_visible else "GuiTreeArrowRight" + _details_btn.icon = get_theme_icon(icon_name, "EditorIcons") + + +func _on_label_pressed(): + _details_container.visible = not _details_container.visible + _adjust_icon(_details_container.visible) + diff --git a/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/details.tscn b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/details.tscn new file mode 100644 index 0000000..b6034ce --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/details.tscn @@ -0,0 +1,60 @@ +[gd_scene load_steps=2 format=3 uid="uid://b1jb5yierm2bq"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/details.gd" id="1_1v4dc"] + +[node name="Details" type="VBoxContainer"] +offset_right = 40.0 +offset_bottom = 40.0 +script = ExtResource("1_1v4dc") + +[node name="label" type="Button" parent="."] +layout_mode = 2 +text = "Details" +flat = true +alignment = 0 + +[node name="MarginContainer" type="MarginContainer" parent="."] +layout_mode = 2 +theme_override_constants/margin_left = 10 + +[node name="GridContainer" type="GridContainer" parent="MarginContainer"] +layout_mode = 2 +theme_override_constants/h_separation = 5 +columns = 2 + +[node name="split_layers_label" type="Label" parent="MarginContainer/GridContainer"] +layout_mode = 2 +text = "Split layers:" + +[node name="split_layers" type="Label" parent="MarginContainer/GridContainer"] +layout_mode = 2 + +[node name="only_visible_layers_label" type="Label" parent="MarginContainer/GridContainer"] +layout_mode = 2 +text = "Only visible layers:" + +[node name="only_visible_layers" type="Label" parent="MarginContainer/GridContainer"] +layout_mode = 2 + +[node name="layer_exclusion_pattern_label" type="Label" parent="MarginContainer/GridContainer"] +layout_mode = 2 +text = "Layer Ex Pattern:" + +[node name="layer_exclusion_pattern" type="Label" parent="MarginContainer/GridContainer"] +layout_mode = 2 + +[node name="output_name_label" type="Label" parent="MarginContainer/GridContainer"] +layout_mode = 2 +text = "Output name:" + +[node name="output_name" type="Label" parent="MarginContainer/GridContainer"] +layout_mode = 2 + +[node name="do_not_create_resource_label" type="Label" parent="MarginContainer/GridContainer"] +layout_mode = 2 +text = "Do not create resource:" + +[node name="do_not_create_resource" type="Label" parent="MarginContainer/GridContainer"] +layout_mode = 2 + +[connection signal="pressed" from="label" to="." method="_on_label_pressed"] diff --git a/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/import_date.gd b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/import_date.gd new file mode 100644 index 0000000..0942752 --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/import_date.gd @@ -0,0 +1,6 @@ +@tool +extends Label + + +func set_date(timestamp: String): + self.text = timestamp diff --git a/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/import_date.tscn b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/import_date.tscn new file mode 100644 index 0000000..aec0cb3 --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/import_date.tscn @@ -0,0 +1,12 @@ +[gd_scene load_steps=2 format=3 uid="uid://bs0i357m5d7ho"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/import_date.gd" id="1"] + +[node name="import_date" type="Label"] +offset_left = 660.0 +offset_top = 39.0 +offset_right = 774.0 +offset_bottom = 53.0 +size_flags_vertical = 0 +text = "2022-07-18 18:01" +script = ExtResource("1") diff --git a/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/list_actions.gd b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/list_actions.gd new file mode 100644 index 0000000..ace8376 --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/list_actions.gd @@ -0,0 +1,20 @@ +@tool +extends HBoxContainer + +signal import_clicked(index) +signal edit_clicked(index) +signal removed_clicked(index) + +var history_index = -1 + + +func _on_edit_pressed(): + emit_signal("edit_clicked", history_index) + + +func _on_reimport_pressed(): + emit_signal("import_clicked", history_index) + + +func _on_remove_pressed(): + emit_signal("removed_clicked", history_index) diff --git a/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/list_actions.tscn b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/list_actions.tscn new file mode 100644 index 0000000..b620fad --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/list_actions.tscn @@ -0,0 +1,35 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/list_actions.gd" id="1"] + +[node name="actions" type="HBoxContainer"] +offset_left = 784.0 +offset_top = 34.0 +offset_right = 950.0 +offset_bottom = 58.0 +custom_constants/separation = 5 +script = ExtResource( 1 ) + +[node name="edit" type="Button" parent="."] +offset_right = 36.0 +offset_bottom = 20.0 +size_flags_vertical = 0 +text = "Edit" + +[node name="reimport" type="Button" parent="."] +offset_left = 41.0 +offset_right = 97.0 +offset_bottom = 20.0 +size_flags_vertical = 0 +text = "Import" + +[node name="remove_at" type="Button" parent="."] +offset_left = 102.0 +offset_right = 166.0 +offset_bottom = 20.0 +size_flags_vertical = 0 +text = "Remove" + +[connection signal="pressed" from="edit" to="." method="_on_edit_pressed"] +[connection signal="pressed" from="reimport" to="." method="_on_reimport_pressed"] +[connection signal="pressed" from="remove_at" to="." method="_on_remove_pressed"] diff --git a/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/output_path.tscn b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/output_path.tscn new file mode 100644 index 0000000..6b9fa5b --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/output_path.tscn @@ -0,0 +1,12 @@ +[gd_scene format=2] + +[node name="output_path" type="LineEdit"] +offset_left = 450.0 +offset_top = 34.0 +offset_right = 650.0 +offset_bottom = 58.0 +minimum_size = Vector2( 200, 0 ) +size_flags_vertical = 0 +text = "/some/output" +align = 3 +editable = false diff --git a/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/source_path.gd b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/source_path.gd new file mode 100644 index 0000000..c49de38 --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/source_path.gd @@ -0,0 +1,6 @@ +@tool +extends LineEdit + +func set_entry(entry: Dictionary): + self.text = entry.source_file + self.tooltip_text = entry.source_file diff --git a/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/source_path.tscn b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/source_path.tscn new file mode 100644 index 0000000..c28e803 --- /dev/null +++ b/addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/source_path.tscn @@ -0,0 +1,18 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/source_path.gd" id="1"] + +[node name="source_path" type="LineEdit"] +offset_top = 34.0 +offset_right = 440.0 +offset_bottom = 58.0 +minimum_size = Vector2( 200, 0 ) +tooltip_text = "Output name / prefix: some_name +Ex. Pattern: $_ +Split: Yes +Only Visible: No +No Resource: No" +size_flags_vertical = 0 +text = "/some/very/long/source/path/to/test/field" +editable = false +script = ExtResource( 1 ) diff --git a/addons/AsepriteWizard/interface/imports_manager/aseprite_imports_manager.gd b/addons/AsepriteWizard/interface/imports_manager/aseprite_imports_manager.gd new file mode 100644 index 0000000..731aba2 --- /dev/null +++ b/addons/AsepriteWizard/interface/imports_manager/aseprite_imports_manager.gd @@ -0,0 +1,5 @@ +@tool +extends Window + +func _on_close_requested(): + self.queue_free() diff --git a/addons/AsepriteWizard/interface/imports_manager/aseprite_imports_manager.tscn b/addons/AsepriteWizard/interface/imports_manager/aseprite_imports_manager.tscn new file mode 100644 index 0000000..444e93b --- /dev/null +++ b/addons/AsepriteWizard/interface/imports_manager/aseprite_imports_manager.tscn @@ -0,0 +1,14 @@ +[gd_scene load_steps=2 format=3 uid="uid://0x3mlxsex7eb"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/imports_manager/aseprite_imports_manager.gd" id="1_wrm15"] + +[node name="AsepriteDockImportsWindow" type="Window"] +title = "Aseprite Imports Manager" +initial_position = 1 +size = Vector2i(612, 458) +wrap_controls = true +transient = true +min_size = Vector2i(600, 400) +script = ExtResource("1_wrm15") + +[connection signal="close_requested" from="." to="." method="_on_close_requested"] diff --git a/addons/AsepriteWizard/interface/imports_manager/dock_imports_panel.gd b/addons/AsepriteWizard/interface/imports_manager/dock_imports_panel.gd new file mode 100644 index 0000000..6efe71b --- /dev/null +++ b/addons/AsepriteWizard/interface/imports_manager/dock_imports_panel.gd @@ -0,0 +1,330 @@ +@tool +extends Panel + +const wizard_config = preload("../../config/wizard_config.gd") + +var _import_helper = preload("./import_helper.gd").new() + +@onready var _tree_container = $MarginContainer/VBoxContainer/HSplitContainer/tree +@onready var _resource_tree = _tree_container.get_resource_tree() +@onready var _details = $MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer + +@onready var _nothing_container = $MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer/nothing +@onready var _single_item_container = $MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer/single_item +@onready var _multiple_items_container = $MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer/multiple_items + +@onready var _confirmation_warning_container = $MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer/confirmation_warning + +const supported_types = [ + "Sprite2D", + "Sprite3D", + "AnimatedSprite2D", + "AnimatedSprite3D", + "TextureRect", +] + +var _selection_count = 0 +var _current_buttons_container +var _resources_to_process + +var _should_save_in = 0 + + +func _ready(): + _set_empty_details_state() + + var file_tree = _get_file_tree("res://") + _setup_tree(file_tree) + + +# Unfortunately godot throws some nasty warnings when trying to save after +# multiple import operations. I implemented this late save as a workaround +func _process(delta): + if _should_save_in > 0: + _should_save_in -= delta + if _should_save_in <= 0: + _should_save_in = 0 + _save_all_scenes() + + +func _get_file_tree(base_path: String, dir_name: String = "") -> Dictionary: + var dir_path = base_path.path_join(dir_name) + var dir = DirAccess.open(dir_path) + var dir_data = { "path": dir_path, "name": dir_name, "children": [], "type": "dir", } + if not dir: + return dir_data + + dir.list_dir_begin() + var file_name = dir.get_next() + + while file_name != "": + if dir.current_is_dir() and _is_importable_folder(dir_path, file_name): + var child_data = _get_file_tree(dir_path, file_name) + if not child_data.children.is_empty(): + dir_data.children.push_back(child_data) + elif file_name.ends_with(".tscn"): + var file_path = dir_path.path_join(file_name) + var metadata = _get_aseprite_metadata(file_path) + if not metadata.is_empty(): + dir_data.children.push_back({ + "name": file_name, + "path": file_path, + "resources": metadata, + "type": "file", + }) + + file_name = dir.get_next() + + return dir_data + + +func _is_importable_folder(dir_path: String, dir_name: String) -> bool: + return dir_path != "res://" or dir_name != "addons" + + +func _setup_tree(resource_tree: Dictionary) -> void: + _resource_tree.set_column_title(0, "Resource") + + var root = _resource_tree.create_item() + + _add_items_to_tree(root, resource_tree.children) + + +func _add_items_to_tree(root: TreeItem, children: Array): + for node in children: + var item: TreeItem = _resource_tree.create_item(root) + item.set_text(0, node.name) + item.set_meta("node", node) + match node.type: + "dir": + item.set_icon(0, get_theme_icon("Folder", "EditorIcons")) + _add_items_to_tree(item, node.children) + + "file": + item.set_icon(0, get_theme_icon("PackedScene", "EditorIcons")) + _add_items_to_tree(item, node.resources) + + "resource": + item.set_icon(0, get_theme_icon(node.node_type, "EditorIcons")) + if node.has_changes: + item.set_text(0, "%s (*)" % node.name) + + +func _get_aseprite_metadata(file_path: String) -> Array: + var scene: PackedScene = load(file_path) + var root = scene.instantiate() + var state = scene.get_state() + + var resources = [] + for i in range(state.get_node_count()): + var node_type = state.get_node_type(i) + if _is_supported_type(node_type): + var node_path = state.get_node_path(i) + var target_node = root.get_node(node_path) + var meta = wizard_config.load_config(target_node) + if meta != null: + resources.push_back({ + "type": "resource", + "node_type": node_type, + "name": node_path, + "node_path": node_path, + "node_name": state.get_node_name(i), + "meta": meta, + "scene_path": file_path, + "has_changes": _has_source_changes(target_node, meta.get("source")) + }) + + return resources + + +func _has_source_changes(target_node: Node, source_path: String) -> bool: + if not source_path or source_path == "": + return false + var saved_hash = wizard_config.get_source_hash(target_node) + if saved_hash == "": + return false + var current_hash = FileAccess.get_md5(source_path) + + return saved_hash != current_hash + + +func _is_supported_type(node_type: String) -> bool: + return supported_types.has(node_type) + + +func _open_scene(item: TreeItem) -> void: + var meta = item.get_meta("node") + if meta: + EditorInterface.open_scene_from_path(meta.path) + + +func _trigger_import(meta: Dictionary) -> void: + # A more elegant way would have been to change the PackedScene directly, however + # during my attempts changing external resources this way was buggy. I decided + # to open and edit the scene via editor with the caveat of having to keep it open afterwards. + EditorInterface.open_scene_from_path(meta.scene_path) + + var root_node = EditorInterface.get_edited_scene_root() + + if not root_node: + printerr("couldn´t open scene %s" % meta.scene_path) + + await _import_helper.import_node(root_node, meta) + + print("Import complete: %s (%s) node from %s" % [ meta.node_path, meta.meta.source, meta.scene_path]) + + +func _on_resource_tree_multi_selected(_item: TreeItem, _column: int, selected: bool) -> void: + _confirmation_warning_container.hide() + _resources_to_process = null + if _current_buttons_container != null: + _current_buttons_container.show_buttons() + + if selected: + _selection_count += 1 + else: + _selection_count -= 1 + + _nothing_container.hide() + _single_item_container.hide() + _multiple_items_container.hide() + + match _selection_count: + 0: + _nothing_container.show() + 1: + _single_item_container.show() + _set_item_details(_resource_tree.get_selected()) + _current_buttons_container = _single_item_container + _: + _multiple_items_container.show() + _multiple_items_container.set_selected_count(_selection_count) + _current_buttons_container = _multiple_items_container + + +func _set_item_details(item: TreeItem) -> void: + if not item.has_meta("node"): + return + var data = item.get_meta("node") + _single_item_container.set_resource_details(data) + + +func _on_multiple_items_import_triggered(): + var selected_item = _resource_tree.get_next_selected(null) + var all_resources = [] + var scenes_to_open = 0 + + while selected_item != null: + scenes_to_open += _set_all_resources(selected_item.get_meta("node"), all_resources) + selected_item = _resource_tree.get_next_selected(selected_item) + + _resources_to_process = all_resources + _show_confirmation_message(scenes_to_open, all_resources.size()) + + +func _on_single_item_import_triggered(): + var selected = _resource_tree.get_selected() + var meta = selected.get_meta("node") + + if meta.type == "resource": + await _trigger_import(_resource_tree.get_selected().get_meta("node")) + _set_tree_item_as_saved(_resource_tree.get_selected()) + _single_item_container.hide_source_change_warning() + EditorInterface.save_scene() + else: + var selected_item = _resource_tree.get_selected() + var all_resources = [] + var scenes_to_open = _set_all_resources(selected_item.get_meta("node"), all_resources) + _resources_to_process = all_resources + _show_confirmation_message(scenes_to_open, all_resources.size()) + + +func _on_single_item_open_scene_triggered(): + var selected_item = _resource_tree.get_selected() + var meta = selected_item.get_meta("node") + if meta.type == "file": + EditorInterface.open_scene_from_path(meta.path) + else: + EditorInterface.open_scene_from_path(meta.scene_path) + + +func _set_all_resources(meta: Dictionary, resources: Array): + var scenes_to_open = 0 + match meta.type: + "dir": + for c in meta.children: + scenes_to_open += _set_all_resources(c, resources) + "file": + scenes_to_open += 1 + for r in meta.resources: + if not resources.has(r): + resources.push_back(r) + "resource": + if not resources.has(meta): + resources.push_back(meta) + return scenes_to_open + + +func _save_all_scenes(): + EditorInterface.save_all_scenes() + _reload_tree() + + +func _show_confirmation_message(scenes: int, resources: int): + _current_buttons_container.hide_buttons() + if scenes > 1: + _confirmation_warning_container.set_message("You are about to open %s scenes and re-import %s resources. Do you wish to continue?" % [scenes, resources]) + else: + _confirmation_warning_container.set_message("You are about to re-import %s resources. Do you wish to continue?" % resources) + + _confirmation_warning_container.show() + + +func _on_resource_tree_refresh_triggered(): + _set_empty_details_state() + _reload_tree() + + +func _reload_tree(): + _confirmation_warning_container.hide() + _resources_to_process = null + if _current_buttons_container != null: + _current_buttons_container.show_buttons() + _current_buttons_container = null + + _selection_count = 0 + _resource_tree.clear() + var file_tree = _get_file_tree("res://") + _setup_tree(file_tree) + + +func _set_empty_details_state(): + _nothing_container.show() + _single_item_container.hide() + _multiple_items_container.hide() + _confirmation_warning_container.hide() + + +func _set_tree_item_as_saved(item: TreeItem) -> void: + var meta = item.get_meta("node") + meta.has_changes = false + item.set_meta("node", meta) + item.set_text(0, meta.name) + + +func _on_confirmation_warning_warning_confirmed(): + _confirmation_warning_container.hide() + _current_buttons_container.show_buttons() + + for resource in _resources_to_process: + await _trigger_import(resource) + EditorInterface.mark_scene_as_unsaved() + _resources_to_process = null + + _should_save_in = 1 + + +func _on_confirmation_warning_warning_declined(): + _confirmation_warning_container.hide() + _current_buttons_container.show_buttons() + _resources_to_process = null diff --git a/addons/AsepriteWizard/interface/imports_manager/dock_imports_panel.tscn b/addons/AsepriteWizard/interface/imports_manager/dock_imports_panel.tscn new file mode 100644 index 0000000..40407f6 --- /dev/null +++ b/addons/AsepriteWizard/interface/imports_manager/dock_imports_panel.tscn @@ -0,0 +1,87 @@ +[gd_scene load_steps=7 format=3 uid="uid://ci67r2f2btg5"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/imports_manager/dock_imports_panel.gd" id="1_1xyeb"] +[ext_resource type="PackedScene" uid="uid://cisgsfvp4nf1g" path="res://addons/AsepriteWizard/interface/shared/tree/resource_tree.tscn" id="2_d1s4o"] +[ext_resource type="PackedScene" uid="uid://qgmln507kjnm" path="res://addons/AsepriteWizard/interface/shared/tree/tree_selection_confirmation_warning.tscn" id="3_2us73"] +[ext_resource type="PackedScene" uid="uid://dnlk2yep7teea" path="res://addons/AsepriteWizard/interface/imports_manager/tree_selection_single_item.tscn" id="3_4ufqa"] +[ext_resource type="PackedScene" uid="uid://bhtu6mlwmthqo" path="res://addons/AsepriteWizard/interface/imports_manager/tree_selection_multiple_items.tscn" id="3_pw8ds"] + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_edrvq"] + +[node name="dock_imports" type="Panel"] +custom_minimum_size = Vector2(600, 400) +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_styles/panel = SubResource("StyleBoxEmpty_edrvq") +script = ExtResource("1_1xyeb") + +[node name="MarginContainer" type="MarginContainer" parent="."] +custom_minimum_size = Vector2(400, 300) +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_constants/margin_left = 10 +theme_override_constants/margin_top = 10 +theme_override_constants/margin_right = 10 +theme_override_constants/margin_bottom = 10 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"] +layout_mode = 2 + +[node name="HSplitContainer" type="HSplitContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="tree" parent="MarginContainer/VBoxContainer/HSplitContainer" instance=ExtResource("2_d1s4o")] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/HSplitContainer"] +custom_minimum_size = Vector2(200, 0) +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/HSplitContainer/MarginContainer"] +layout_mode = 2 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer/MarginContainer"] +layout_mode = 2 +text = "Details" +horizontal_alignment = 1 + +[node name="HSeparator" type="HSeparator" parent="MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="nothing" type="Label" parent="MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 6 +text = "Nothing selected" +horizontal_alignment = 1 + +[node name="single_item" parent="MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer" instance=ExtResource("3_4ufqa")] +layout_mode = 2 + +[node name="multiple_items" parent="MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer" instance=ExtResource("3_pw8ds")] +layout_mode = 2 + +[node name="confirmation_warning" parent="MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer" instance=ExtResource("3_2us73")] +layout_mode = 2 + +[connection signal="multi_selected" from="MarginContainer/VBoxContainer/HSplitContainer/tree" to="." method="_on_resource_tree_multi_selected"] +[connection signal="refresh_triggered" from="MarginContainer/VBoxContainer/HSplitContainer/tree" to="." method="_on_resource_tree_refresh_triggered"] +[connection signal="import_triggered" from="MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer/single_item" to="." method="_on_single_item_import_triggered"] +[connection signal="open_scene_triggered" from="MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer/single_item" to="." method="_on_single_item_open_scene_triggered"] +[connection signal="import_triggered" from="MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer/multiple_items" to="." method="_on_multiple_items_import_triggered"] +[connection signal="warning_confirmed" from="MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer/confirmation_warning" to="." method="_on_confirmation_warning_warning_confirmed"] +[connection signal="warning_declined" from="MarginContainer/VBoxContainer/HSplitContainer/MarginContainer/VBoxContainer/confirmation_warning" to="." method="_on_confirmation_warning_warning_declined"] diff --git a/addons/AsepriteWizard/interface/imports_manager/import_helper.gd b/addons/AsepriteWizard/interface/imports_manager/import_helper.gd new file mode 100644 index 0000000..433e6f8 --- /dev/null +++ b/addons/AsepriteWizard/interface/imports_manager/import_helper.gd @@ -0,0 +1,123 @@ +@tool +extends RefCounted + +const wizard_config = preload("../../config/wizard_config.gd") +const result_code = preload("../../config/result_codes.gd") + +var _sprite_animation_creator = preload("../../creators/animation_player/sprite_animation_creator.gd").new() +var _texture_rect_animation_creator = preload("../../creators/animation_player/texture_rect_animation_creator.gd").new() +var _static_texture_creator = preload("../../creators/static_texture/texture_creator.gd").new() +var _sprite_frames_creator = preload("../../creators/sprite_frames/sprite_frames_creator.gd").new() +var _aseprite_file_exporter = preload("../../aseprite/file_exporter.gd").new() +var _config = preload("../../config/config.gd").new() + + +func import_node(root_node: Node, meta: Dictionary) -> void: + var node = root_node.get_node(meta.node_path) + + if node == null: + printerr("Node not found: %s" % meta.node_path) + return + + if node is AnimatedSprite2D or node is AnimatedSprite3D: + await _sprite_frames_import(node, meta) + else: + await _animation_import(node, root_node, meta) + + +func _sprite_frames_import(node: Node, resource_config: Dictionary) -> void: + var config = resource_config.meta + if not config.source: + printerr("Node config missing information.") + return + + var source = ProjectSettings.globalize_path(config.source) + var options = _parse_import_options(config, resource_config.scene_path.get_base_dir()) + + var aseprite_output = _aseprite_file_exporter.generate_aseprite_file(source, options) + + if not aseprite_output.is_ok: + printerr(result_code.get_error_message(aseprite_output.code)) + return + + EditorInterface.get_resource_filesystem().scan() + + await EditorInterface.get_resource_filesystem().filesystem_changed + + _sprite_frames_creator.create_animations(node, aseprite_output.content, { "slice": options.slice }) + + wizard_config.set_source_hash(node, FileAccess.get_md5(source)) + _handle_cleanup(aseprite_output.content) + + +func _animation_import(node: Node, root_node: Node, resource_config: Dictionary) -> void: + if not resource_config.meta.source: + printerr("Node config missing information.") + return + + if resource_config.meta.get("i_mode", 0) == 0: + await _import_to_animation_player(node, root_node, resource_config) + else: + await _import_static(node, resource_config) + + +func _import_to_animation_player(node: Node, root: Node, resource_config: Dictionary) -> void: + var config = resource_config.meta + var source = ProjectSettings.globalize_path(config.source) + var options = _parse_import_options(config, resource_config.scene_path.get_base_dir()) + + var aseprite_output = _aseprite_file_exporter.generate_aseprite_file(source, options) + if not aseprite_output.is_ok: + printerr(result_code.get_error_message(aseprite_output.code)) + return + + EditorInterface.get_resource_filesystem().scan() + await EditorInterface.get_resource_filesystem().filesystem_changed + + var anim_options = { + "keep_anim_length": config.keep_anim_length, + "cleanup_hide_unused_nodes": config.get("set_vis_track"), + "slice": config.get("slice", ""), + } + var animation_creator = _texture_rect_animation_creator if node is TextureRect else _sprite_animation_creator + animation_creator.create_animations(node, root.get_node(config.player), aseprite_output.content, anim_options) + + wizard_config.set_source_hash(node, FileAccess.get_md5(source)) + _handle_cleanup(aseprite_output.content) + + +func _import_static(node: Node, resource_config: Dictionary) -> void: + var config = resource_config.meta + var source = ProjectSettings.globalize_path(config.source) + var options = _parse_import_options(config, resource_config.scene_path.get_base_dir()) + options["first_frame_only"] = true + + var aseprite_output = _aseprite_file_exporter.generate_aseprite_file(source, options) + + if not aseprite_output.is_ok: + printerr(result_code.get_error_message(aseprite_output.code)) + return + + EditorInterface.get_resource_filesystem().scan() + await EditorInterface.get_resource_filesystem().filesystem_changed + + _static_texture_creator.load_texture(node, aseprite_output.content, { "slice": options.slice }) + + wizard_config.set_source_hash(node, FileAccess.get_md5(source)) + _handle_cleanup(aseprite_output.content) + + +func _parse_import_options(config: Dictionary, scene_base_path: String) -> Dictionary: + return { + "output_folder": config.o_folder if config.o_folder != "" else scene_base_path, + "exception_pattern": config.o_ex_p, + "only_visible_layers": config.only_visible, + "output_filename": config.o_name, + "layer": config.get("layer"), + "slice": config.get("slice", ""), + } + +func _handle_cleanup(aseprite_content): + if _config.should_remove_source_files(): + DirAccess.remove_absolute(aseprite_content.data_file) + EditorInterface.get_resource_filesystem().scan() diff --git a/addons/AsepriteWizard/interface/imports_manager/import_panels.gd b/addons/AsepriteWizard/interface/imports_manager/import_panels.gd new file mode 100644 index 0000000..c972dd5 --- /dev/null +++ b/addons/AsepriteWizard/interface/imports_manager/import_panels.gd @@ -0,0 +1,28 @@ +@tool +extends MarginContainer + +signal dock_requested + +enum Tabs { + DOCK_IMPORTS = 0, +} + +@onready var _tabs: TabContainer = $TabContainer +@onready var _dock_button: Button = $dock_button + +func _ready(): + _tabs.set_tab_title(Tabs.DOCK_IMPORTS, "Dock Imports") + _dock_button.icon = get_theme_icon("MakeFloating", "EditorIcons") + set_as_floating() + + +func _on_dock_button_pressed(): + dock_requested.emit() + + +func set_as_floating(): + _dock_button.tooltip_text = "Dock window" + + +func set_as_docked(): + _dock_button.tooltip_text = "Undock window" diff --git a/addons/AsepriteWizard/interface/imports_manager/import_panels.tscn b/addons/AsepriteWizard/interface/imports_manager/import_panels.tscn new file mode 100644 index 0000000..2083f3d --- /dev/null +++ b/addons/AsepriteWizard/interface/imports_manager/import_panels.tscn @@ -0,0 +1,42 @@ +[gd_scene load_steps=5 format=3 uid="uid://bae2rn6vwdx88"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/imports_manager/import_panels.gd" id="1_853ds"] +[ext_resource type="PackedScene" uid="uid://ci67r2f2btg5" path="res://addons/AsepriteWizard/interface/imports_manager/dock_imports_panel.tscn" id="2_rtubf"] + +[sub_resource type="Image" id="Image_47yuu"] +data = { +"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 1, 224, 224, 224, 82, 225, 225, 225, 126, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 114, 230, 230, 230, 30, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 225, 225, 225, 143, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 238, 229, 229, 229, 29, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 242, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 113, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 64, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 128, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 149, 149, 149, 24, 146, 146, 146, 112, 146, 146, 146, 128, 146, 146, 146, 128, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 128, 255, 255, 255, 0, 255, 255, 255, 0, 149, 149, 149, 24, 145, 145, 145, 231, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 128, 255, 255, 255, 0, 255, 255, 255, 0, 146, 146, 146, 112, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 128, 255, 255, 255, 0, 255, 255, 255, 0, 146, 146, 146, 128, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 128, 255, 255, 255, 0, 255, 255, 255, 0, 146, 146, 146, 128, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 128, 255, 255, 255, 0, 255, 255, 255, 0, 146, 146, 146, 128, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 128, 255, 255, 255, 0, 255, 255, 255, 0, 146, 146, 146, 128, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 128, 255, 255, 255, 0, 255, 255, 255, 0, 146, 146, 146, 128, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 220, 220, 220, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 112, 255, 255, 255, 0, 255, 255, 255, 0, 146, 146, 146, 128, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 189, 189, 189, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 255, 224, 224, 224, 237, 227, 227, 227, 27, 255, 255, 255, 0, 255, 255, 255, 0, 146, 146, 146, 128, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 170, 170, 170, 255, 185, 185, 185, 255, 184, 184, 184, 255, 184, 184, 184, 255, 184, 184, 184, 255, 184, 184, 184, 255, 184, 184, 184, 255, 184, 184, 184, 255, 184, 184, 184, 255, 184, 184, 184, 255, 184, 184, 184, 255, 224, 224, 224, 128, 224, 224, 224, 128, 224, 224, 224, 112, 227, 227, 227, 27, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 146, 146, 146, 128, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 146, 146, 146, 128, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 146, 146, 146, 128, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 146, 146, 146, 112, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 239, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 149, 149, 149, 24, 145, 145, 145, 231, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 145, 145, 145, 255, 146, 146, 146, 126, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 149, 149, 149, 24, 145, 145, 145, 111, 146, 146, 146, 128, 146, 146, 146, 128, 146, 146, 146, 128, 146, 146, 146, 128, 146, 146, 146, 128, 146, 146, 146, 128, 146, 146, 146, 128, 146, 146, 146, 128, 146, 146, 146, 128, 146, 146, 146, 128, 146, 146, 146, 128, 146, 146, 146, 126, 145, 145, 145, 72, 255, 255, 255, 1, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0), +"format": "RGBA8", +"height": 24, +"mipmaps": false, +"width": 24 +} + +[sub_resource type="ImageTexture" id="ImageTexture_qam8x"] +image = SubResource("Image_47yuu") + +[node name="ImportPanels" type="MarginContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +script = ExtResource("1_853ds") + +[node name="TabContainer" type="TabContainer" parent="."] +layout_mode = 2 + +[node name="dock_imports" parent="TabContainer" instance=ExtResource("2_rtubf")] +layout_mode = 2 +metadata/_tab_name = "Dock Imports" + +[node name="dock_button" type="Button" parent="."] +layout_mode = 2 +size_flags_horizontal = 8 +size_flags_vertical = 0 +tooltip_text = "Dock window" +icon = SubResource("ImageTexture_qam8x") + +[connection signal="pressed" from="dock_button" to="." method="_on_dock_button_pressed"] diff --git a/addons/AsepriteWizard/interface/imports_manager/tree_filter_field.gd b/addons/AsepriteWizard/interface/imports_manager/tree_filter_field.gd new file mode 100644 index 0000000..51fa19b --- /dev/null +++ b/addons/AsepriteWizard/interface/imports_manager/tree_filter_field.gd @@ -0,0 +1,22 @@ +@tool +extends LineEdit + +signal change_finished(text: String) + +@export var debounce_time_in_seconds: float = 0.3 + +var _time_since_last_change: float = 0.0 +var _has_pending_changes: bool = false + + +func _process(delta: float) -> void: + if _has_pending_changes: + _time_since_last_change += delta + if _time_since_last_change > debounce_time_in_seconds: + _has_pending_changes = false + change_finished.emit(self.text) + + +func _on_text_changed(_new_text: String) -> void: + _has_pending_changes = true + _time_since_last_change = 0 diff --git a/addons/AsepriteWizard/interface/imports_manager/tree_selection_multiple_items.gd b/addons/AsepriteWizard/interface/imports_manager/tree_selection_multiple_items.gd new file mode 100644 index 0000000..cf8882b --- /dev/null +++ b/addons/AsepriteWizard/interface/imports_manager/tree_selection_multiple_items.gd @@ -0,0 +1,22 @@ +@tool +extends VBoxContainer + +signal import_triggered + +@onready var _import_message = $message +@onready var _import_button = $buttons + +func set_selected_count(number_of_items: int) -> void: + _import_message.text = "%2d items selected" % number_of_items + + +func show_buttons(): + _import_button.show() + + +func hide_buttons(): + _import_button.hide() + + +func _on_import_selected_button_up(): + import_triggered.emit() diff --git a/addons/AsepriteWizard/interface/imports_manager/tree_selection_multiple_items.tscn b/addons/AsepriteWizard/interface/imports_manager/tree_selection_multiple_items.tscn new file mode 100644 index 0000000..a59a220 --- /dev/null +++ b/addons/AsepriteWizard/interface/imports_manager/tree_selection_multiple_items.tscn @@ -0,0 +1,25 @@ +[gd_scene load_steps=2 format=3 uid="uid://bhtu6mlwmthqo"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/imports_manager/tree_selection_multiple_items.gd" id="1_beamo"] + +[node name="multiple_items" type="VBoxContainer"] +visible = false +size_flags_vertical = 3 +script = ExtResource("1_beamo") + +[node name="message" type="Label" parent="."] +layout_mode = 2 +size_flags_vertical = 6 +text = "Multiple items selected +" +horizontal_alignment = 1 + +[node name="buttons" type="HFlowContainer" parent="."] +layout_mode = 2 + +[node name="import_selected" type="Button" parent="buttons"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Re-Import all" + +[connection signal="button_up" from="buttons/import_selected" to="." method="_on_import_selected_button_up"] diff --git a/addons/AsepriteWizard/interface/imports_manager/tree_selection_single_item.gd b/addons/AsepriteWizard/interface/imports_manager/tree_selection_single_item.gd new file mode 100644 index 0000000..5d512df --- /dev/null +++ b/addons/AsepriteWizard/interface/imports_manager/tree_selection_single_item.gd @@ -0,0 +1,141 @@ +@tool +extends VBoxContainer + +signal import_triggered +signal open_scene_triggered + +@onready var _name = $GridContainer/name_value +@onready var _type = $GridContainer/type_value +@onready var _path = $GridContainer/path_value + +@onready var _source_label = $GridContainer/source_file_label +@onready var _source = $GridContainer/source_file_value +@onready var _layer_label = $GridContainer/layer_label +@onready var _layer = $GridContainer/layer_value +@onready var _slice_label = $GridContainer/slice_label +@onready var _slice = $GridContainer/slice_value + +@onready var _o_name_label = $GridContainer/o_name_label +@onready var _o_name = $GridContainer/o_name_value +@onready var _o_folder_label = $GridContainer/o_folder_label +@onready var _o_folder_value = $GridContainer/o_folder_value + + +@onready var _resource_buttons = $resource_buttons +@onready var _dir_buttons = $dir_buttons +@onready var _scene_buttons = $scene_buttons + +@onready var _source_change_warning = $source_changed_warning + +@onready var _resource_only_fields = [ + _source_label, + _source, + _layer_label, + _layer, + _slice_label, + _slice, + _o_name_label, + _o_name, + _o_folder_label, + _o_folder_value, + _source_change_warning, +] + +var _current_resource_type = "resource" +var _resource_config: Dictionary = {} + +func _ready(): + _source_change_warning.set_text("Source file changed since last import") + _source_change_warning.hide() + + +func set_resource_details(resource_details: Dictionary) -> void: + _resource_config = resource_details + _resource_buttons.hide() + _dir_buttons.hide() + _scene_buttons.hide() + _source_change_warning.hide() + + _current_resource_type = resource_details.type + match resource_details.type: + "dir": + _name.text = resource_details.name + _type.text = "Folder" + _path.text = resource_details.path + _hide_resource_fields() + _dir_buttons.show() + "file": + _name.text = resource_details.name + _type.text = "File" + _path.text = resource_details.path + _hide_resource_fields() + _scene_buttons.show() + "resource": + _name.text = resource_details.node_name + _type.text = resource_details.node_type + _path.text = resource_details.node_path + + var meta = resource_details.meta + _source.text = meta.source + _layer.text = "All" if meta.get("layer", "") == "" else meta.layer + _slice.text = "All" if meta.get("slice", "") == "" else meta.slice + + var folder = resource_details.scene_path.get_base_dir() if meta.get("o_folder", "") == "" else meta.o_folder + var file_name = "" if meta.get("o_name", "") == "" else meta.o_name + + if _layer.text != "All": + file_name += _layer.text + elif file_name == "": + file_name = meta.source.get_basename().get_file() + _o_name.text = "%s/%s.png" % [folder, file_name] + + _show_resource_fields() + _resource_buttons.show() + + _source_change_warning.visible = resource_details.has_changes + + +func _hide_resource_fields(): + for f in _resource_only_fields: + f.hide() + + +func _show_resource_fields(): + for f in _resource_only_fields: + f.show() + + +func show_buttons(): + match _current_resource_type: + "resource": + _resource_buttons.show() + "scene": + _scene_buttons.show() + _: + _dir_buttons.show() + + +func hide_buttons(): + _resource_buttons.hide() + _dir_buttons.hide() + _scene_buttons.hide() + + +func hide_source_change_warning(): + _source_change_warning.hide() + + +func _on_show_dir_in_fs_button_up(): + EditorInterface.get_file_system_dock().navigate_to_path(_path.text) + + +func _on_import_all_button_up(): + import_triggered.emit() + + +func _on_import_button_up(): + import_triggered.emit() + + +func _on_open_scene_button_up(): + open_scene_triggered.emit() diff --git a/addons/AsepriteWizard/interface/imports_manager/tree_selection_single_item.tscn b/addons/AsepriteWizard/interface/imports_manager/tree_selection_single_item.tscn new file mode 100644 index 0000000..914b180 --- /dev/null +++ b/addons/AsepriteWizard/interface/imports_manager/tree_selection_single_item.tscn @@ -0,0 +1,158 @@ +[gd_scene load_steps=3 format=3 uid="uid://dnlk2yep7teea"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/imports_manager/tree_selection_single_item.gd" id="1_bb3ui"] +[ext_resource type="PackedScene" uid="uid://c1l0bk12iwln3" path="res://addons/AsepriteWizard/interface/shared/tree/inline_warning_panel.tscn" id="2_weuqf"] + +[node name="single_item" type="VBoxContainer"] +visible = false +script = ExtResource("1_bb3ui") + +[node name="GridContainer" type="GridContainer" parent="."] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_constants/h_separation = 10 +columns = 2 + +[node name="type_label" type="Label" parent="GridContainer"] +layout_mode = 2 +size_flags_vertical = 0 +text = "Type" + +[node name="type_value" type="Label" parent="GridContainer"] +layout_mode = 2 +text = "-" + +[node name="name_label" type="Label" parent="GridContainer"] +layout_mode = 2 +size_flags_vertical = 0 +text = "Name" + +[node name="name_value" type="Label" parent="GridContainer"] +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +size_flags_horizontal = 3 +text = "-" +autowrap_mode = 2 + +[node name="path_label" type="Label" parent="GridContainer"] +layout_mode = 2 +size_flags_vertical = 0 +text = "Path" + +[node name="path_value" type="Label" parent="GridContainer"] +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +size_flags_horizontal = 3 +text = "-" +autowrap_mode = 1 + +[node name="HSeparator" type="HSeparator" parent="GridContainer"] +layout_mode = 2 + +[node name="HSeparator2" type="HSeparator" parent="GridContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="source_file_label" type="Label" parent="GridContainer"] +layout_mode = 2 +size_flags_vertical = 0 +text = "Aseprite File" + +[node name="source_file_value" type="Label" parent="GridContainer"] +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +size_flags_horizontal = 3 +text = "-" +autowrap_mode = 1 + +[node name="layer_label" type="Label" parent="GridContainer"] +layout_mode = 2 +size_flags_vertical = 0 +text = "Layer" + +[node name="layer_value" type="Label" parent="GridContainer"] +layout_mode = 2 +text = "-" + +[node name="slice_label" type="Label" parent="GridContainer"] +layout_mode = 2 +size_flags_vertical = 0 +text = "Slice" + +[node name="slice_value" type="Label" parent="GridContainer"] +layout_mode = 2 +text = "-" + +[node name="o_folder_label" type="Label" parent="GridContainer"] +visible = false +layout_mode = 2 +size_flags_vertical = 0 +text = "Output folder" + +[node name="o_folder_value" type="Label" parent="GridContainer"] +visible = false +custom_minimum_size = Vector2(50, 0) +layout_mode = 2 +size_flags_horizontal = 3 +text = "-" + +[node name="o_name_label" type="Label" parent="GridContainer"] +layout_mode = 2 +size_flags_vertical = 0 +text = "Spritesheet name" + +[node name="o_name_value" type="Label" parent="GridContainer"] +layout_mode = 2 +text = "-" + +[node name="source_changed_warning" parent="." instance=ExtResource("2_weuqf")] +layout_mode = 2 + +[node name="resource_buttons" type="HFlowContainer" parent="."] +visible = false +layout_mode = 2 + +[node name="import" type="Button" parent="resource_buttons"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Re-Import" + +[node name="open_scene" type="Button" parent="resource_buttons"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Open Scene" + +[node name="scene_buttons" type="HFlowContainer" parent="."] +visible = false +layout_mode = 2 + +[node name="import_all" type="Button" parent="scene_buttons"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Re-Import all" + +[node name="open_scene" type="Button" parent="scene_buttons"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Open Scene" + +[node name="dir_buttons" type="HFlowContainer" parent="."] +visible = false +layout_mode = 2 + +[node name="import_all" type="Button" parent="dir_buttons"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Re-Import all" + +[node name="show_dir_in_fs" type="Button" parent="dir_buttons"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Show In FileSystem" + +[connection signal="button_up" from="resource_buttons/import" to="." method="_on_import_button_up"] +[connection signal="button_up" from="resource_buttons/open_scene" to="." method="_on_open_scene_button_up"] +[connection signal="button_up" from="scene_buttons/import_all" to="." method="_on_import_all_button_up"] +[connection signal="button_up" from="scene_buttons/open_scene" to="." method="_on_open_scene_button_up"] +[connection signal="button_up" from="dir_buttons/import_all" to="." method="_on_import_all_button_up"] +[connection signal="button_up" from="dir_buttons/show_dir_in_fs" to="." method="_on_show_dir_in_fs_button_up"] diff --git a/addons/AsepriteWizard/interface/shared/animation_player_drop_button.gd b/addons/AsepriteWizard/interface/shared/animation_player_drop_button.gd new file mode 100644 index 0000000..dbcd0ac --- /dev/null +++ b/addons/AsepriteWizard/interface/shared/animation_player_drop_button.gd @@ -0,0 +1,15 @@ +@tool +extends Button + +signal node_dropped(node_path) + +func _can_drop_data(_pos, data): + if data.type == "nodes": + var node = get_node(data.nodes[0]) + return node is AnimationPlayer + return false + + +func _drop_data(_pos, data): + var path = data.nodes[0] + node_dropped.emit(path) diff --git a/addons/AsepriteWizard/interface/shared/animation_player_drop_button.tscn b/addons/AsepriteWizard/interface/shared/animation_player_drop_button.tscn new file mode 100644 index 0000000..14148a5 --- /dev/null +++ b/addons/AsepriteWizard/interface/shared/animation_player_drop_button.tscn @@ -0,0 +1,12 @@ +[gd_scene load_steps=2 format=3 uid="uid://x1f1t87m582u"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/shared/animation_player_drop_button.gd" id="1_tfmxw"] + +[node name="options" type="OptionButton"] +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +item_count = 1 +selected = 0 +popup/item_0/text = "[empty]" +popup/item_0/id = 0 +script = ExtResource("1_tfmxw") diff --git a/addons/AsepriteWizard/interface/shared/dir_drop_button.gd b/addons/AsepriteWizard/interface/shared/dir_drop_button.gd new file mode 100644 index 0000000..42c5489 --- /dev/null +++ b/addons/AsepriteWizard/interface/shared/dir_drop_button.gd @@ -0,0 +1,14 @@ +@tool +extends Button + +signal dir_dropped(path) + +func _can_drop_data(_pos, data): + if data.type == "files_and_dirs": + var dir_access = DirAccess.open(data.files[0]) + return dir_access != null + return false + + +func _drop_data(_pos, data): + dir_dropped.emit(data.files[0]) diff --git a/addons/AsepriteWizard/interface/shared/dir_drop_button.tscn b/addons/AsepriteWizard/interface/shared/dir_drop_button.tscn new file mode 100644 index 0000000..332d3cc --- /dev/null +++ b/addons/AsepriteWizard/interface/shared/dir_drop_button.tscn @@ -0,0 +1,10 @@ +[gd_scene load_steps=2 format=3 uid="uid://cwvgnm3o7eed2"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/shared/dir_drop_button.gd" id="1_7uqph"] + +[node name="button" type="Button"] +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "[empty]" +clip_text = true +script = ExtResource("1_7uqph") diff --git a/addons/AsepriteWizard/interface/shared/source_drop_button.gd b/addons/AsepriteWizard/interface/shared/source_drop_button.gd new file mode 100644 index 0000000..3fdeed2 --- /dev/null +++ b/addons/AsepriteWizard/interface/shared/source_drop_button.gd @@ -0,0 +1,15 @@ +@tool +extends Button + +signal aseprite_file_dropped(path) + +func _can_drop_data(_pos, data): + if data.type == "files": + var extension = data.files[0].get_extension() + return extension == "ase" or extension == "aseprite" + return false + + +func _drop_data(_pos, data): + var path = data.files[0] + aseprite_file_dropped.emit(path) diff --git a/addons/AsepriteWizard/interface/shared/source_drop_button.tscn b/addons/AsepriteWizard/interface/shared/source_drop_button.tscn new file mode 100644 index 0000000..dc0c25e --- /dev/null +++ b/addons/AsepriteWizard/interface/shared/source_drop_button.tscn @@ -0,0 +1,10 @@ +[gd_scene load_steps=2 format=3 uid="uid://dj1uo3blocr8e"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/shared/source_drop_button.gd" id="1_smfgi"] + +[node name="source_drop_button" type="Button"] +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +text = "[empty]" +clip_text = true +script = ExtResource("1_smfgi") diff --git a/addons/AsepriteWizard/interface/shared/tree/inline_warning_panel.gd b/addons/AsepriteWizard/interface/shared/tree/inline_warning_panel.gd new file mode 100644 index 0000000..3678c65 --- /dev/null +++ b/addons/AsepriteWizard/interface/shared/tree/inline_warning_panel.gd @@ -0,0 +1,19 @@ +@tool +extends PanelContainer + +@onready var _message = $MarginContainer/HBoxContainer/Label + +func _ready(): + _configure_source_warning() + + +func _configure_source_warning(): + var sb = self.get_theme_stylebox("panel") + var color = EditorInterface.get_editor_settings().get_setting("interface/theme/accent_color") + color.a = 0.2 + sb.bg_color = color + self.get_node("MarginContainer/HBoxContainer/Icon").texture = get_theme_icon("NodeInfo", "EditorIcons") + + +func set_text(text: String): + _message.text = text diff --git a/addons/AsepriteWizard/interface/shared/tree/inline_warning_panel.tscn b/addons/AsepriteWizard/interface/shared/tree/inline_warning_panel.tscn new file mode 100644 index 0000000..9a61018 --- /dev/null +++ b/addons/AsepriteWizard/interface/shared/tree/inline_warning_panel.tscn @@ -0,0 +1,51 @@ +[gd_scene load_steps=5 format=3 uid="uid://c1l0bk12iwln3"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/shared/tree/inline_warning_panel.gd" id="1_aomsj"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_rpkoa"] +bg_color = Color(0.44, 0.73, 0.98, 0.2) +border_width_left = 10 +border_color = Color(0.8, 0.8, 0.8, 0) +corner_radius_top_left = 5 +corner_radius_top_right = 5 +corner_radius_bottom_right = 5 +corner_radius_bottom_left = 5 + +[sub_resource type="Image" id="Image_6c2qv"] +data = { +"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 43, 255, 255, 255, 78, 255, 255, 255, 112, 255, 255, 255, 111, 255, 255, 255, 78, 255, 255, 255, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 12, 255, 255, 255, 107, 255, 255, 255, 212, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 210, 255, 255, 255, 105, 255, 255, 255, 10, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 47, 255, 255, 255, 219, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 217, 255, 255, 255, 44, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 84, 255, 255, 255, 247, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 245, 255, 255, 255, 80, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 45, 255, 255, 255, 246, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 244, 255, 255, 255, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 10, 255, 255, 255, 217, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 128, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 128, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 216, 255, 255, 255, 10, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 104, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 128, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 128, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 101, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 211, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 128, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 128, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 207, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 44, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 39, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 81, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 192, 255, 255, 255, 128, 255, 255, 255, 128, 255, 255, 255, 192, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 76, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 114, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 128, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 128, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 110, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 114, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 128, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 128, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 109, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 81, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 128, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 128, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 75, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 43, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 128, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 128, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 37, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 210, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 128, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 128, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 204, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 104, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 128, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 128, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 97, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 10, 255, 255, 255, 217, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 128, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 128, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 212, 255, 255, 255, 8, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 44, 255, 255, 255, 245, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 242, 255, 255, 255, 39, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 81, 255, 255, 255, 245, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 243, 255, 255, 255, 75, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 44, 255, 255, 255, 217, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 214, 255, 255, 255, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 10, 255, 255, 255, 102, 255, 255, 255, 208, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 207, 255, 255, 255, 100, 255, 255, 255, 9, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 38, 255, 255, 255, 75, 255, 255, 255, 110, 255, 255, 255, 110, 255, 255, 255, 75, 255, 255, 255, 38, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0), +"format": "RGBA8", +"height": 24, +"mipmaps": false, +"width": 24 +} + +[sub_resource type="ImageTexture" id="ImageTexture_7t6c7"] +image = SubResource("Image_6c2qv") + +[node name="inline_warning_panel" type="PanelContainer"] +visible = false +theme_override_styles/panel = SubResource("StyleBoxFlat_rpkoa") +script = ExtResource("1_aomsj") + +[node name="MarginContainer" type="MarginContainer" parent="."] +layout_mode = 2 +theme_override_constants/margin_left = 10 +theme_override_constants/margin_right = 10 + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer"] +layout_mode = 2 + +[node name="Icon" type="TextureRect" parent="MarginContainer/HBoxContainer"] +modulate = Color(1, 1, 1, 0.486275) +custom_minimum_size = Vector2(16, 0) +layout_mode = 2 +texture = SubResource("ImageTexture_7t6c7") +stretch_mode = 5 + +[node name="Label" type="Label" parent="MarginContainer/HBoxContainer"] +custom_minimum_size = Vector2(10, 0) +layout_mode = 2 +size_flags_horizontal = 3 +text = "Source file changed since last import" +autowrap_mode = 2 diff --git a/addons/AsepriteWizard/interface/shared/tree/resource_tree.gd b/addons/AsepriteWizard/interface/shared/tree/resource_tree.gd new file mode 100644 index 0000000..38d32d9 --- /dev/null +++ b/addons/AsepriteWizard/interface/shared/tree/resource_tree.gd @@ -0,0 +1,55 @@ +@tool +extends VBoxContainer + +signal refresh_triggered +signal multi_selected(item: TreeItem, column: int, selected: bool) + +@onready var _tree: Tree = $Tree + +func _on_tree_filter_change_finished(text): + var tree_root: TreeItem = _tree.get_root() + + if text == "": + tree_root.call_recursive("set", "visible", true) + return + tree_root.call_recursive("set", "visible", false) + + _make_matching_children_visible(tree_root, text.to_lower()) + + +func _make_matching_children_visible(tree_root: TreeItem, text: String) -> void: + for c in tree_root.get_children(): + if c.get_text(0).to_lower().contains(text): + c.visible = true + _ensure_parent_visible(c) + _make_matching_children_visible(c, text) + + +func _ensure_parent_visible(tree_item: TreeItem) -> void: + var node_parent = tree_item.get_parent() + if node_parent != null and not node_parent.visible: + node_parent.visible = true + _ensure_parent_visible(node_parent) + + +func _on_expand_all_pressed(): + var tree_root: TreeItem = _tree.get_root() + tree_root.set_collapsed_recursive(false) + + +func _on_collapse_all_pressed(): + var tree_root: TreeItem = _tree.get_root() + tree_root.set_collapsed_recursive(true) + tree_root.collapsed = false + + +func _on_refresh_tree_pressed(): + refresh_triggered.emit() + + +func _on_tree_multi_selected(item: TreeItem, column: int, selected: bool): + multi_selected.emit(item, column, selected) + + +func get_resource_tree() -> Tree: + return _tree diff --git a/addons/AsepriteWizard/interface/shared/tree/resource_tree.tscn b/addons/AsepriteWizard/interface/shared/tree/resource_tree.tscn new file mode 100644 index 0000000..222694c --- /dev/null +++ b/addons/AsepriteWizard/interface/shared/tree/resource_tree.tscn @@ -0,0 +1,51 @@ +[gd_scene load_steps=3 format=3 uid="uid://cisgsfvp4nf1g"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/shared/tree/resource_tree.gd" id="1_io4rc"] +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/imports_manager/tree_filter_field.gd" id="1_q7epo"] + +[node name="resource_tree" type="VBoxContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 3 +script = ExtResource("1_io4rc") + +[node name="buttons" type="HFlowContainer" parent="."] +layout_mode = 2 + +[node name="tree_filter" type="LineEdit" parent="buttons"] +layout_mode = 2 +size_flags_horizontal = 3 +placeholder_text = "Filter..." +clear_button_enabled = true +script = ExtResource("1_q7epo") + +[node name="expand_all" type="Button" parent="buttons"] +custom_minimum_size = Vector2(150, 0) +layout_mode = 2 +text = "Expand All" + +[node name="collapse_all" type="Button" parent="buttons"] +custom_minimum_size = Vector2(150, 0) +layout_mode = 2 +text = "Collapse All" + +[node name="refresh_tree" type="Button" parent="buttons"] +custom_minimum_size = Vector2(150, 0) +layout_mode = 2 +text = "Refresh Tree" + +[node name="Tree" type="Tree" parent="."] +layout_mode = 2 +size_flags_vertical = 3 +hide_root = true +select_mode = 2 + +[connection signal="change_finished" from="buttons/tree_filter" to="." method="_on_tree_filter_change_finished"] +[connection signal="text_changed" from="buttons/tree_filter" to="buttons/tree_filter" method="_on_text_changed"] +[connection signal="pressed" from="buttons/expand_all" to="." method="_on_expand_all_pressed"] +[connection signal="pressed" from="buttons/collapse_all" to="." method="_on_collapse_all_pressed"] +[connection signal="pressed" from="buttons/refresh_tree" to="." method="_on_refresh_tree_pressed"] +[connection signal="multi_selected" from="Tree" to="." method="_on_tree_multi_selected"] diff --git a/addons/AsepriteWizard/interface/shared/tree/tree_selection_confirmation_warning.gd b/addons/AsepriteWizard/interface/shared/tree/tree_selection_confirmation_warning.gd new file mode 100644 index 0000000..8086db1 --- /dev/null +++ b/addons/AsepriteWizard/interface/shared/tree/tree_selection_confirmation_warning.gd @@ -0,0 +1,18 @@ +@tool +extends VBoxContainer + +signal warning_confirmed +signal warning_declined + +@onready var _warning_message = $MarginContainer/warning_message + +func set_message(text: String) -> void: + _warning_message.text = text + + +func _on_confirm_button_up(): + warning_confirmed.emit() + + +func _on_cancel_button_up(): + warning_declined.emit() diff --git a/addons/AsepriteWizard/interface/shared/tree/tree_selection_confirmation_warning.tscn b/addons/AsepriteWizard/interface/shared/tree/tree_selection_confirmation_warning.tscn new file mode 100644 index 0000000..828f71a --- /dev/null +++ b/addons/AsepriteWizard/interface/shared/tree/tree_selection_confirmation_warning.tscn @@ -0,0 +1,34 @@ +[gd_scene load_steps=2 format=3 uid="uid://qgmln507kjnm"] + +[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/shared/tree/tree_selection_confirmation_warning.gd" id="1_7gtu1"] + +[node name="confirmation_warning" type="VBoxContainer"] +script = ExtResource("1_7gtu1") + +[node name="MarginContainer" type="MarginContainer" parent="."] +layout_mode = 2 +theme_override_constants/margin_left = 10 +theme_override_constants/margin_top = 10 +theme_override_constants/margin_right = 10 +theme_override_constants/margin_bottom = 10 + +[node name="warning_message" type="Label" parent="MarginContainer"] +custom_minimum_size = Vector2(100, 0) +layout_mode = 2 +autowrap_mode = 2 + +[node name="buttons" type="HFlowContainer" parent="."] +layout_mode = 2 + +[node name="confirm" type="Button" parent="buttons"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Confirm" + +[node name="cancel" type="Button" parent="buttons"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "Cancel" + +[connection signal="button_up" from="buttons/confirm" to="." method="_on_confirm_button_up"] +[connection signal="button_up" from="buttons/cancel" to="." method="_on_cancel_button_up"] diff --git a/addons/AsepriteWizard/plugin.cfg b/addons/AsepriteWizard/plugin.cfg new file mode 100644 index 0000000..5c70c09 --- /dev/null +++ b/addons/AsepriteWizard/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="Aseprite Wizard" +description="Import Aseprite files to Godot in many different ways." +author="Vinicius Gerevini" +version="7.5.0" +script="plugin.gd" diff --git a/addons/AsepriteWizard/plugin.gd b/addons/AsepriteWizard/plugin.gd new file mode 100644 index 0000000..fdda8e2 --- /dev/null +++ b/addons/AsepriteWizard/plugin.gd @@ -0,0 +1,208 @@ +@tool +extends EditorPlugin + +# importers +const NoopImportPlugin = preload("importers/noop_import_plugin.gd") +const SpriteFramesImportPlugin = preload("importers/sprite_frames_import_plugin.gd") +const TilesetTextureImportPlugin = preload("importers/tileset_texture_import_plugin.gd") +const TextureImportPlugin = preload("importers/static_texture_import_plugin.gd") + +# export +const ExportPlugin = preload("export/metadata_export_plugin.gd") +# interface +const ConfigDialog = preload('config/config_dialog.tscn') +const WizardWindow = preload("interface/docks/wizard/as_wizard_dock_container.tscn") +const AsepriteDockImportsWindow = preload('interface/imports_manager/aseprite_imports_manager.tscn') +const ImportsManagerPanels = preload('interface/imports_manager/import_panels.tscn') + +const AnimatedSpriteInspectorPlugin = preload("interface/docks/animated_sprite/inspector_plugin.gd") +const SpriteInspectorPlugin = preload("interface/docks/sprite/inspector_plugin.gd") + +const tool_menu_name = "Aseprite Wizard" +const menu_item_name = "Spritesheet Wizard Dock..." +const config_menu_item_name = "Config..." +const import_menu_item_name = "Imports Manager..." + +var config = preload("config/config.gd").new() +var window: TabContainer +var config_window: PopupPanel +var imports_list_window: Window +var imports_list_panel: MarginContainer +var export_plugin : EditorExportPlugin +var sprite_inspector_plugin: EditorInspectorPlugin +var animated_sprite_inspector_plugin: EditorInspectorPlugin + +var _exporter_enabled = false + +var _importers = [] + +var _is_import_list_docked = false + +func _enter_tree(): + _load_config() + _setup_menu_entries() + _setup_importer() + _setup_exporter() + _setup_animated_sprite_inspector_plugin() + _setup_sprite_inspector_plugin() + + +func _exit_tree(): + _disable_plugin() + + +func _disable_plugin(): + _remove_menu_entries() + _remove_importer() + _remove_exporter() + _remove_wizard_dock() + _remove_inspector_plugins() + + +func _load_config(): + config.initialize_project_settings() + + +func _setup_menu_entries(): + var submenu = PopupMenu.new() + add_tool_submenu_item(tool_menu_name, submenu) + submenu.add_item(menu_item_name) + submenu.add_item(import_menu_item_name) + submenu.add_item(config_menu_item_name) + submenu.index_pressed.connect(_on_tool_menu_pressed) + + +func _remove_menu_entries(): + remove_tool_menu_item(tool_menu_name) + + +func _setup_importer(): + _importers = [ + NoopImportPlugin.new(), + SpriteFramesImportPlugin.new(), + TilesetTextureImportPlugin.new(), + TextureImportPlugin.new(), + ] + + for i in _importers: + add_import_plugin(i) + + +func _remove_importer(): + for i in _importers: + remove_import_plugin(i) + + +func _setup_exporter(): + if config.is_exporter_enabled(): + export_plugin = ExportPlugin.new() + add_export_plugin(export_plugin) + _exporter_enabled = true + + +func _remove_exporter(): + if _exporter_enabled: + remove_export_plugin(export_plugin) + _exporter_enabled = false + + +func _setup_sprite_inspector_plugin(): + sprite_inspector_plugin = SpriteInspectorPlugin.new() + add_inspector_plugin(sprite_inspector_plugin) + + +func _setup_animated_sprite_inspector_plugin(): + animated_sprite_inspector_plugin = AnimatedSpriteInspectorPlugin.new() + add_inspector_plugin(animated_sprite_inspector_plugin) + + +func _remove_inspector_plugins(): + remove_inspector_plugin(sprite_inspector_plugin) + remove_inspector_plugin(animated_sprite_inspector_plugin) + + +func _remove_wizard_dock(): + if window: + remove_control_from_bottom_panel(window) + window.queue_free() + window = null + + +func _open_window(): + if window: + make_bottom_panel_item_visible(window) + return + + window = WizardWindow.instantiate() + window.connect("close_requested",Callable(self,"_on_window_closed")) + add_control_to_bottom_panel(window, "Aseprite Wizard") + make_bottom_panel_item_visible(window) + + +func _open_config_dialog(): + if is_instance_valid(config_window): + config_window.queue_free() + + config_window = ConfigDialog.instantiate() + get_editor_interface().get_base_control().add_child(config_window) + config_window.popup_centered() + + +func _open_import_list_dialog(): + if is_instance_valid(imports_list_window): + imports_list_window.queue_free() + + if is_instance_valid(imports_list_panel): + if _is_import_list_docked: + remove_control_from_bottom_panel(imports_list_panel) + _is_import_list_docked = false + imports_list_panel.queue_free() + imports_list_panel = null + + imports_list_panel = ImportsManagerPanels.instantiate() + imports_list_panel.dock_requested.connect(_on_import_list_dock_requested) + _create_imports_manager_window(imports_list_panel) + + +func _on_window_closed(): + if window: + remove_control_from_bottom_panel(window) + window.queue_free() + window = null + + +func _on_tool_menu_pressed(index): + match index: + 0: # wizard dock + _open_window() + 1: # imports + _open_import_list_dialog() + 2: # config + _open_config_dialog() + + +func _on_import_list_dock_requested(): + if _is_import_list_docked: + remove_control_from_bottom_panel(imports_list_panel) + _is_import_list_docked = false + _create_imports_manager_window(imports_list_panel) + imports_list_panel.show() + imports_list_panel.anchors_preset = Control.PRESET_FULL_RECT + imports_list_panel.size_flags_vertical = Control.SIZE_EXPAND_FILL + imports_list_panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL + imports_list_panel.set_as_floating() + return + + _is_import_list_docked = true + imports_list_panel.set_as_docked() + imports_list_window.remove_child(imports_list_panel) + imports_list_window.queue_free() + add_control_to_bottom_panel(imports_list_panel, "Aseprite Imports Manager") + make_bottom_panel_item_visible(imports_list_panel) + + +func _create_imports_manager_window(panel: MarginContainer): + imports_list_window = AsepriteDockImportsWindow.instantiate() + imports_list_window.add_child(panel) + get_editor_interface().get_base_control().add_child(imports_list_window) + imports_list_window.popup_centered_ratio(0.5) diff --git a/assets/entities.aseprite b/assets/entities.aseprite new file mode 100644 index 0000000..72d9e32 Binary files /dev/null and b/assets/entities.aseprite differ diff --git a/assets/entities.aseprite.import b/assets/entities.aseprite.import new file mode 100644 index 0000000..6dc2459 --- /dev/null +++ b/assets/entities.aseprite.import @@ -0,0 +1,16 @@ +[remap] + +importer="aseprite_wizard.plugin.tileset-texture" +type="AtlasTexture" +uid="uid://baj1vbwkmk26f" +path="res://.godot/imported/entities.aseprite-23faa5535f255f2080a8da226d2dcf46.res" + +[deps] + +source_file="res://assets/entities.aseprite" +dest_files=["res://.godot/imported/entities.aseprite-23faa5535f255f2080a8da226d2dcf46.res"] + +[params] + +exclude_layers_pattern="" +only_visible_layers=false diff --git a/assets/entities.png b/assets/entities.png new file mode 100644 index 0000000..bbb3c93 Binary files /dev/null and b/assets/entities.png differ diff --git a/assets/entities.png.import b/assets/entities.png.import new file mode 100644 index 0000000..790d43c --- /dev/null +++ b/assets/entities.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bdnapk5ubpkp5" +path="res://.godot/imported/entities.png-9c8d7a7e2d883326840f865160aae000.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/entities.png" +dest_files=["res://.godot/imported/entities.png-9c8d7a7e2d883326840f865160aae000.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/assets/fonts/RobotoSlab-Black.ttf b/assets/fonts/RobotoSlab-Black.ttf new file mode 100644 index 0000000..164f684 Binary files /dev/null and b/assets/fonts/RobotoSlab-Black.ttf differ diff --git a/assets/fonts/RobotoSlab-Black.ttf.import b/assets/fonts/RobotoSlab-Black.ttf.import new file mode 100644 index 0000000..daf7c5f --- /dev/null +++ b/assets/fonts/RobotoSlab-Black.ttf.import @@ -0,0 +1,33 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://dmyxgu8nlvfgo" +path="res://.godot/imported/RobotoSlab-Black.ttf-968535c5805faf9e791d6a4f99705469.fontdata" + +[deps] + +source_file="res://assets/fonts/RobotoSlab-Black.ttf" +dest_files=["res://.godot/imported/RobotoSlab-Black.ttf-968535c5805faf9e791d6a4f99705469.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +hinting=1 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/assets/fonts/RobotoSlab-Bold.ttf b/assets/fonts/RobotoSlab-Bold.ttf new file mode 100644 index 0000000..8a597f2 Binary files /dev/null and b/assets/fonts/RobotoSlab-Bold.ttf differ diff --git a/assets/fonts/RobotoSlab-Bold.ttf.import b/assets/fonts/RobotoSlab-Bold.ttf.import new file mode 100644 index 0000000..3a2ba63 --- /dev/null +++ b/assets/fonts/RobotoSlab-Bold.ttf.import @@ -0,0 +1,33 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://hy2xc55kvde1" +path="res://.godot/imported/RobotoSlab-Bold.ttf-06dcbcfac68c891ddcadb2a179a6048c.fontdata" + +[deps] + +source_file="res://assets/fonts/RobotoSlab-Bold.ttf" +dest_files=["res://.godot/imported/RobotoSlab-Bold.ttf-06dcbcfac68c891ddcadb2a179a6048c.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +hinting=1 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/assets/fonts/RobotoSlab-ExtraBold.ttf b/assets/fonts/RobotoSlab-ExtraBold.ttf new file mode 100644 index 0000000..e60d02c Binary files /dev/null and b/assets/fonts/RobotoSlab-ExtraBold.ttf differ diff --git a/assets/fonts/RobotoSlab-ExtraBold.ttf.import b/assets/fonts/RobotoSlab-ExtraBold.ttf.import new file mode 100644 index 0000000..2b3d72b --- /dev/null +++ b/assets/fonts/RobotoSlab-ExtraBold.ttf.import @@ -0,0 +1,33 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://bm8p71lrttw3i" +path="res://.godot/imported/RobotoSlab-ExtraBold.ttf-8d5c4ede71ce63c738779b5005ffac1c.fontdata" + +[deps] + +source_file="res://assets/fonts/RobotoSlab-ExtraBold.ttf" +dest_files=["res://.godot/imported/RobotoSlab-ExtraBold.ttf-8d5c4ede71ce63c738779b5005ffac1c.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +hinting=1 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/assets/fonts/RobotoSlab-ExtraLight.ttf b/assets/fonts/RobotoSlab-ExtraLight.ttf new file mode 100644 index 0000000..8d93c4b Binary files /dev/null and b/assets/fonts/RobotoSlab-ExtraLight.ttf differ diff --git a/assets/fonts/RobotoSlab-ExtraLight.ttf.import b/assets/fonts/RobotoSlab-ExtraLight.ttf.import new file mode 100644 index 0000000..79de668 --- /dev/null +++ b/assets/fonts/RobotoSlab-ExtraLight.ttf.import @@ -0,0 +1,33 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://brk3g8m42rtrr" +path="res://.godot/imported/RobotoSlab-ExtraLight.ttf-ae238b0832bf5fdb340c7cbd70558b7b.fontdata" + +[deps] + +source_file="res://assets/fonts/RobotoSlab-ExtraLight.ttf" +dest_files=["res://.godot/imported/RobotoSlab-ExtraLight.ttf-ae238b0832bf5fdb340c7cbd70558b7b.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +hinting=1 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/assets/fonts/RobotoSlab-Light.ttf b/assets/fonts/RobotoSlab-Light.ttf new file mode 100644 index 0000000..078cb8d Binary files /dev/null and b/assets/fonts/RobotoSlab-Light.ttf differ diff --git a/assets/fonts/RobotoSlab-Light.ttf.import b/assets/fonts/RobotoSlab-Light.ttf.import new file mode 100644 index 0000000..dd84d99 --- /dev/null +++ b/assets/fonts/RobotoSlab-Light.ttf.import @@ -0,0 +1,33 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://dvsxehbnqm6wh" +path="res://.godot/imported/RobotoSlab-Light.ttf-d0d00fe5400b238c04568e98228c6502.fontdata" + +[deps] + +source_file="res://assets/fonts/RobotoSlab-Light.ttf" +dest_files=["res://.godot/imported/RobotoSlab-Light.ttf-d0d00fe5400b238c04568e98228c6502.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +hinting=1 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/assets/fonts/RobotoSlab-Medium.ttf b/assets/fonts/RobotoSlab-Medium.ttf new file mode 100644 index 0000000..4437720 Binary files /dev/null and b/assets/fonts/RobotoSlab-Medium.ttf differ diff --git a/assets/fonts/RobotoSlab-Medium.ttf.import b/assets/fonts/RobotoSlab-Medium.ttf.import new file mode 100644 index 0000000..86e133e --- /dev/null +++ b/assets/fonts/RobotoSlab-Medium.ttf.import @@ -0,0 +1,33 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://h4kcttshyx1m" +path="res://.godot/imported/RobotoSlab-Medium.ttf-1a3ceb4696d918b4779c96cb85640733.fontdata" + +[deps] + +source_file="res://assets/fonts/RobotoSlab-Medium.ttf" +dest_files=["res://.godot/imported/RobotoSlab-Medium.ttf-1a3ceb4696d918b4779c96cb85640733.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +hinting=1 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/assets/fonts/RobotoSlab-Regular.ttf b/assets/fonts/RobotoSlab-Regular.ttf new file mode 100644 index 0000000..faaa446 Binary files /dev/null and b/assets/fonts/RobotoSlab-Regular.ttf differ diff --git a/assets/fonts/RobotoSlab-Regular.ttf.import b/assets/fonts/RobotoSlab-Regular.ttf.import new file mode 100644 index 0000000..32e11ca --- /dev/null +++ b/assets/fonts/RobotoSlab-Regular.ttf.import @@ -0,0 +1,33 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://baasmkq0oy8vh" +path="res://.godot/imported/RobotoSlab-Regular.ttf-011ed363d621fdefb75a3af3649ba3db.fontdata" + +[deps] + +source_file="res://assets/fonts/RobotoSlab-Regular.ttf" +dest_files=["res://.godot/imported/RobotoSlab-Regular.ttf-011ed363d621fdefb75a3af3649ba3db.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +hinting=1 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/assets/fonts/RobotoSlab-SemiBold.ttf b/assets/fonts/RobotoSlab-SemiBold.ttf new file mode 100644 index 0000000..28f71ee Binary files /dev/null and b/assets/fonts/RobotoSlab-SemiBold.ttf differ diff --git a/assets/fonts/RobotoSlab-SemiBold.ttf.import b/assets/fonts/RobotoSlab-SemiBold.ttf.import new file mode 100644 index 0000000..8d813d5 --- /dev/null +++ b/assets/fonts/RobotoSlab-SemiBold.ttf.import @@ -0,0 +1,33 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://bgenrcrfodt1q" +path="res://.godot/imported/RobotoSlab-SemiBold.ttf-59a2b85741555f743dbc69a41cdc163b.fontdata" + +[deps] + +source_file="res://assets/fonts/RobotoSlab-SemiBold.ttf" +dest_files=["res://.godot/imported/RobotoSlab-SemiBold.ttf-59a2b85741555f743dbc69a41cdc163b.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +hinting=1 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/assets/fonts/RobotoSlab-Thin.ttf b/assets/fonts/RobotoSlab-Thin.ttf new file mode 100644 index 0000000..f945998 Binary files /dev/null and b/assets/fonts/RobotoSlab-Thin.ttf differ diff --git a/assets/fonts/RobotoSlab-Thin.ttf.import b/assets/fonts/RobotoSlab-Thin.ttf.import new file mode 100644 index 0000000..9c601c9 --- /dev/null +++ b/assets/fonts/RobotoSlab-Thin.ttf.import @@ -0,0 +1,33 @@ +[remap] + +importer="font_data_dynamic" +type="FontFile" +uid="uid://cs862txqr6w8c" +path="res://.godot/imported/RobotoSlab-Thin.ttf-28bb0b3304a481f2dca6e8c09b6199fc.fontdata" + +[deps] + +source_file="res://assets/fonts/RobotoSlab-Thin.ttf" +dest_files=["res://.godot/imported/RobotoSlab-Thin.ttf-28bb0b3304a481f2dca6e8c09b6199fc.fontdata"] + +[params] + +Rendering=null +antialiasing=1 +generate_mipmaps=false +multichannel_signed_distance_field=false +msdf_pixel_range=8 +msdf_size=48 +allow_system_fallback=true +force_autohinter=false +hinting=1 +subpixel_positioning=1 +oversampling=0.0 +Fallbacks=null +fallbacks=[] +Compress=null +compress=true +preload=[] +language_support={} +script_support={} +opentype_features={} diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..3fe4f4a --- /dev/null +++ b/icon.svg @@ -0,0 +1 @@ + diff --git a/icon.svg.import b/icon.svg.import new file mode 100644 index 0000000..acb622d --- /dev/null +++ b/icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bvt5hq8u65buv" +path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.svg" +dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 +svg/scale=1.0 +editor/scale_with_editor_scale=false +editor/convert_colors_with_editor_theme=false diff --git a/project.godot b/project.godot new file mode 100644 index 0000000..b660a8b --- /dev/null +++ b/project.godot @@ -0,0 +1,69 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=5 + +[application] + +config/name="MisguidedLaunch" +run/main_scene="res://scenes/launch_menu.tscn" +config/features=PackedStringArray("4.2", "Forward Plus") +config/icon="res://icon.svg" + +[autoload] + +DataHandler="*res://scripts/data_handler.gd" + +[debug] + +gdscript/warnings/untyped_declaration=1 + +[editor_plugins] + +enabled=PackedStringArray("res://addons/AsepriteWizard/plugin.cfg") + +[file_customization] + +folder_colors={ +"res://assets/": "yellow", +"res://resources/": "red", +"res://scenes/": "purple", +"res://scripts/": "teal" +} + +[input] + +move_up={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"echo":false,"script":null) +] +} +move_down={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"echo":false,"script":null) +] +} +move_left={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"echo":false,"script":null) +] +} +move_right={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"echo":false,"script":null) +] +} +eject={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"echo":false,"script":null) +] +} + +[rendering] + +textures/canvas_textures/default_texture_filter=0 diff --git a/resources/launch_menu_theme.tres b/resources/launch_menu_theme.tres new file mode 100644 index 0000000..6fe636f --- /dev/null +++ b/resources/launch_menu_theme.tres @@ -0,0 +1,11 @@ +[gd_resource type="Theme" load_steps=2 format=3 uid="uid://1pisbpunwphc"] + +[ext_resource type="FontFile" uid="uid://baasmkq0oy8vh" path="res://assets/fonts/RobotoSlab-Regular.ttf" id="1_r2v8d"] + +[resource] +default_font = ExtResource("1_r2v8d") +MarginContainer/constants/margin_bottom = 64 +MarginContainer/constants/margin_left = 32 +MarginContainer/constants/margin_right = 32 +MarginContainer/constants/margin_top = 64 +VBoxContainer/constants/separation = 8 diff --git a/scenes/asteroid.tscn b/scenes/asteroid.tscn new file mode 100644 index 0000000..804871d --- /dev/null +++ b/scenes/asteroid.tscn @@ -0,0 +1,18 @@ +[gd_scene load_steps=4 format=3 uid="uid://dkx8cdbyjy1h0"] + +[ext_resource type="Texture2D" uid="uid://bdnapk5ubpkp5" path="res://assets/entities.png" id="1_5y7g0"] + +[sub_resource type="AtlasTexture" id="AtlasTexture_rwpvg"] +atlas = ExtResource("1_5y7g0") +region = Rect2(32, 0, 32, 32) + +[sub_resource type="CircleShape2D" id="CircleShape2D_nfnwd"] +radius = 12.0 + +[node name="Asteroid" type="CharacterBody2D"] + +[node name="Sprite2D" type="Sprite2D" parent="."] +texture = SubResource("AtlasTexture_rwpvg") + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +shape = SubResource("CircleShape2D_nfnwd") diff --git a/scenes/cargo_item.tscn b/scenes/cargo_item.tscn new file mode 100644 index 0000000..8f81b1d --- /dev/null +++ b/scenes/cargo_item.tscn @@ -0,0 +1,30 @@ +[gd_scene load_steps=4 format=3 uid="uid://rgn8815eiygi"] + +[ext_resource type="Script" path="res://scripts/cargo_item.gd" id="1_8jou3"] +[ext_resource type="Texture2D" uid="uid://bdnapk5ubpkp5" path="res://assets/entities.png" id="2_ikmyd"] + +[sub_resource type="AtlasTexture" id="AtlasTexture_s1wtw"] +atlas = ExtResource("2_ikmyd") +region = Rect2(128, 0, 32, 32) + +[node name="CargoItem" type="PanelContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_8jou3") + +[node name="HBoxContainer" type="HBoxContainer" parent="."] +layout_mode = 2 + +[node name="ItemTexture" type="TextureRect" parent="HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +texture = SubResource("AtlasTexture_s1wtw") +stretch_mode = 4 + +[node name="ItemLabel" type="Label" parent="HBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "x0" diff --git a/scenes/configuration_menu.tscn b/scenes/configuration_menu.tscn new file mode 100644 index 0000000..090dfc6 --- /dev/null +++ b/scenes/configuration_menu.tscn @@ -0,0 +1,62 @@ +[gd_scene load_steps=4 format=3 uid="uid://dsvbx2ye5u82v"] + +[ext_resource type="Theme" uid="uid://1pisbpunwphc" path="res://resources/launch_menu_theme.tres" id="1_7lr8t"] +[ext_resource type="PackedScene" uid="uid://dn2djggxwl22c" path="res://scenes/module_selector.tscn" id="2_gmtor"] +[ext_resource type="Script" path="res://scripts/configuration_menu.gd" id="2_nfsxa"] + +[node name="ConfigurationMenu" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme = ExtResource("1_7lr8t") +script = ExtResource("2_nfsxa") + +[node name="MarginContainer" type="MarginContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"] +layout_mode = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="ReturnButton" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +text = "Return" + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_type_variation = &"HeaderMedium" +text = "Rocket Configuration" +horizontal_alignment = 1 + +[node name="HSeparator" type="HSeparator" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="ModulesContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_vertical = 3 + +[node name="Spacer" type="Control" parent="MarginContainer/VBoxContainer/ModulesContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="ModuleSelector" parent="MarginContainer/VBoxContainer/ModulesContainer" instance=ExtResource("2_gmtor")] +layout_mode = 2 + +[node name="ModuleSelector2" parent="MarginContainer/VBoxContainer/ModulesContainer" instance=ExtResource("2_gmtor")] +layout_mode = 2 + +[node name="Spacer2" type="Control" parent="MarginContainer/VBoxContainer/ModulesContainer"] +layout_mode = 2 +size_flags_horizontal = 3 diff --git a/scenes/cost_container.tscn b/scenes/cost_container.tscn new file mode 100644 index 0000000..1c49ef1 --- /dev/null +++ b/scenes/cost_container.tscn @@ -0,0 +1,34 @@ +[gd_scene load_steps=3 format=3 uid="uid://u13yhxooh61n"] + +[ext_resource type="Script" path="res://scripts/cost_container.gd" id="1_vbysc"] + +[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_vy8d6"] + +[node name="CostContainer" type="BoxContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_vbysc") + +[node name="HBoxContainer" type="HBoxContainer" parent="."] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="ResourceTexture" type="TextureRect" parent="HBoxContainer"] +layout_mode = 2 +texture = SubResource("PlaceholderTexture2D_vy8d6") +expand_mode = 3 + +[node name="CostTextEdit" type="LineEdit" parent="HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +text = "20" +editable = false +context_menu_enabled = false +virtual_keyboard_enabled = false +shortcut_keys_enabled = false +middle_mouse_paste_enabled = false +selecting_enabled = false +drag_and_drop_selection_enabled = false diff --git a/scenes/fuel_tank.tscn b/scenes/fuel_tank.tscn new file mode 100644 index 0000000..a9c2537 --- /dev/null +++ b/scenes/fuel_tank.tscn @@ -0,0 +1,21 @@ +[gd_scene load_steps=5 format=3 uid="uid://b1lpxtvsq3whv"] + +[ext_resource type="Script" path="res://scripts/fuel_tank.gd" id="1_botbj"] +[ext_resource type="Texture2D" uid="uid://bdnapk5ubpkp5" path="res://assets/entities.png" id="2_lg4by"] + +[sub_resource type="AtlasTexture" id="AtlasTexture_kcbvc"] +atlas = ExtResource("2_lg4by") +region = Rect2(64, 0, 32, 32) + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_chjnb"] +size = Vector2(22, 23) + +[node name="FuelTank" type="CharacterBody2D" groups=["pickups"]] +script = ExtResource("1_botbj") + +[node name="Sprite2D" type="Sprite2D" parent="."] +texture = SubResource("AtlasTexture_kcbvc") + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +position = Vector2(1, 1.5) +shape = SubResource("RectangleShape2D_chjnb") diff --git a/scenes/inventory_menu.tscn b/scenes/inventory_menu.tscn new file mode 100644 index 0000000..551f0d3 --- /dev/null +++ b/scenes/inventory_menu.tscn @@ -0,0 +1,50 @@ +[gd_scene load_steps=3 format=3 uid="uid://ckh1x0u0k6ijq"] + +[ext_resource type="Theme" uid="uid://1pisbpunwphc" path="res://resources/launch_menu_theme.tres" id="1_4vmg6"] +[ext_resource type="Script" path="res://scripts/inventory_menu.gd" id="2_7tmo5"] + +[node name="InventoryMenu" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme = ExtResource("1_4vmg6") +script = ExtResource("2_7tmo5") + +[node name="MarginContainer" type="MarginContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"] +layout_mode = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="ReturnButton" type="Button" parent="MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +text = "Return" + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer/HBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_type_variation = &"HeaderMedium" +text = "Inventory" +horizontal_alignment = 1 + +[node name="HSeparator" type="HSeparator" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="PanelContainer" type="PanelContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="InventoryContainer" type="HFlowContainer" parent="MarginContainer/VBoxContainer/PanelContainer"] +unique_name_in_owner = true +layout_mode = 2 diff --git a/scenes/launch_menu.tscn b/scenes/launch_menu.tscn new file mode 100644 index 0000000..2468a75 --- /dev/null +++ b/scenes/launch_menu.tscn @@ -0,0 +1,127 @@ +[gd_scene load_steps=6 format=3 uid="uid://dfgy2g62bnlsy"] + +[ext_resource type="Script" path="res://scripts/launch_menu.gd" id="1_jq4be"] +[ext_resource type="Theme" uid="uid://1pisbpunwphc" path="res://resources/launch_menu_theme.tres" id="2_7vnj5"] +[ext_resource type="Texture2D" uid="uid://bdnapk5ubpkp5" path="res://assets/entities.png" id="3_uaai1"] + +[sub_resource type="AtlasTexture" id="AtlasTexture_m1xkl"] +atlas = ExtResource("3_uaai1") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_1qr3l"] +atlas = ExtResource("3_uaai1") +region = Rect2(192, 0, 32, 32) + +[node name="LaunchMenu" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_jq4be") + +[node name="MenuRoot" type="HBoxContainer" parent="."] +unique_name_in_owner = true +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="LeftSide" type="MarginContainer" parent="MenuRoot"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 2.0 +theme = ExtResource("2_7vnj5") + +[node name="VBoxContainer" type="VBoxContainer" parent="MenuRoot/LeftSide"] +layout_mode = 2 + +[node name="Title" type="Label" parent="MenuRoot/LeftSide/VBoxContainer"] +layout_mode = 2 +theme = ExtResource("2_7vnj5") +theme_override_font_sizes/font_size = 64 +text = "Misguided Launch" +horizontal_alignment = 1 + +[node name="Spacer" type="Control" parent="MenuRoot/LeftSide/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="RocketDisplayContainer" type="HBoxContainer" parent="MenuRoot/LeftSide/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_vertical = 3 + +[node name="SpacerStart" type="Control" parent="MenuRoot/LeftSide/VBoxContainer/RocketDisplayContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="TextureEngine" type="TextureRect" parent="MenuRoot/LeftSide/VBoxContainer/RocketDisplayContainer"] +layout_mode = 2 +size_flags_vertical = 3 +texture = SubResource("AtlasTexture_m1xkl") +expand_mode = 3 +stretch_mode = 4 + +[node name="TextureHead" type="TextureRect" parent="MenuRoot/LeftSide/VBoxContainer/RocketDisplayContainer"] +layout_mode = 2 +size_flags_vertical = 3 +texture = SubResource("AtlasTexture_1qr3l") +expand_mode = 3 +stretch_mode = 4 + +[node name="SpacerEnd" type="Control" parent="MenuRoot/LeftSide/VBoxContainer/RocketDisplayContainer"] +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Spacer2" type="Control" parent="MenuRoot/LeftSide/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="HBoxContainer" type="HBoxContainer" parent="MenuRoot/LeftSide/VBoxContainer"] +layout_mode = 2 + +[node name="SettingsButton" type="Button" parent="MenuRoot/LeftSide/VBoxContainer/HBoxContainer"] +layout_mode = 2 +text = "Settings" + +[node name="RightMenu" type="MarginContainer" parent="MenuRoot"] +layout_mode = 2 +size_flags_horizontal = 3 +theme = ExtResource("2_7vnj5") + +[node name="ButtonContainer" type="VBoxContainer" parent="MenuRoot/RightMenu"] +layout_mode = 2 + +[node name="UpgradesButton" type="Button" parent="MenuRoot/RightMenu/ButtonContainer"] +layout_mode = 2 +text = "Upgrades" + +[node name="ConfigurationButton" type="Button" parent="MenuRoot/RightMenu/ButtonContainer"] +layout_mode = 2 +text = "Rocket Configuration" + +[node name="StatisticsButton" type="Button" parent="MenuRoot/RightMenu/ButtonContainer"] +layout_mode = 2 +text = "Statistics" + +[node name="InventoryButton" type="Button" parent="MenuRoot/RightMenu/ButtonContainer"] +layout_mode = 2 +text = "Inventory" + +[node name="Spacer" type="Control" parent="MenuRoot/RightMenu/ButtonContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="LaunchButton" type="Button" parent="MenuRoot/RightMenu/ButtonContainer"] +custom_minimum_size = Vector2(2.08165e-12, 128) +layout_mode = 2 +text = "Launch!" + +[connection signal="pressed" from="MenuRoot/RightMenu/ButtonContainer/UpgradesButton" to="." method="_on_upgrades_button_pressed"] +[connection signal="pressed" from="MenuRoot/RightMenu/ButtonContainer/ConfigurationButton" to="." method="_on_configuration_button_pressed"] +[connection signal="pressed" from="MenuRoot/RightMenu/ButtonContainer/InventoryButton" to="." method="_on_inventory_button_pressed"] +[connection signal="pressed" from="MenuRoot/RightMenu/ButtonContainer/LaunchButton" to="." method="_on_launch_button_pressed"] diff --git a/scenes/module_selector.tscn b/scenes/module_selector.tscn new file mode 100644 index 0000000..56659d9 --- /dev/null +++ b/scenes/module_selector.tscn @@ -0,0 +1,54 @@ +[gd_scene load_steps=3 format=3 uid="uid://dn2djggxwl22c"] + +[ext_resource type="Script" path="res://scripts/module_selector.gd" id="1_77nb8"] + +[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_j4ao8"] + +[node name="ModuleSelector" type="VBoxContainer"] +anchors_preset = 9 +anchor_bottom = 1.0 +offset_right = 117.0 +grow_vertical = 2 +script = ExtResource("1_77nb8") + +[node name="LocationLabel" type="Label" parent="."] +unique_name_in_owner = true +layout_mode = 2 +text = "Location" +horizontal_alignment = 1 + +[node name="ModuleSelectorOption" type="OptionButton" parent="."] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +item_count = 2 +popup/item_0/text = "Module 1" +popup/item_0/id = 0 +popup/item_1/text = "Module 2" +popup/item_1/id = 1 + +[node name="ModuleTexture" type="TextureRect" parent="."] +unique_name_in_owner = true +modulate = Color(0, 0, 0, 1) +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +texture = SubResource("PlaceholderTexture2D_j4ao8") +expand_mode = 3 +stretch_mode = 4 + +[node name="Panel" type="Panel" parent="."] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="DescriptionLabel" type="Label" parent="Panel"] +unique_name_in_owner = true +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +text = "Module Description" +autowrap_mode = 3 diff --git a/scenes/rocket.tscn b/scenes/rocket.tscn new file mode 100644 index 0000000..330c74c --- /dev/null +++ b/scenes/rocket.tscn @@ -0,0 +1,82 @@ +[gd_scene load_steps=10 format=3 uid="uid://doxwblduu0no0"] + +[ext_resource type="Script" path="res://scripts/rocket.gd" id="1_tmk40"] +[ext_resource type="Texture2D" uid="uid://bdnapk5ubpkp5" path="res://assets/entities.png" id="1_un6dn"] + +[sub_resource type="AtlasTexture" id="AtlasTexture_admdg"] +atlas = ExtResource("1_un6dn") +region = Rect2(192, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_li1r2"] +atlas = ExtResource("1_un6dn") +region = Rect2(224, 0, 32, 32) + +[sub_resource type="CapsuleShape2D" id="CapsuleShape2D_1uivg"] +radius = 12.0 +height = 32.0 + +[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_0l0cx"] +particle_flag_disable_z = true +direction = Vector3(-1, 2.08165e-12, 2.08165e-12) +spread = 90.0 +initial_velocity_max = 16.0 +gravity = Vector3(2.08165e-12, 2.08165e-12, 2.08165e-12) +color = Color(1, 0.427451, 0.686275, 1) +hue_variation_max = 0.3 + +[sub_resource type="Curve" id="Curve_sx08r"] +min_value = -1.0 +_data = [Vector2(0.0529801, 85.8977), 0.0, 0.0, 0, 0, Vector2(0.135762, 0.243075), -2.95911, -2.95911, 0, 0, Vector2(0.155629, 0.250424), 0.0, 0.0, 0, 0, Vector2(1, -0.851894), 0.0, 0.0, 0, 0] +point_count = 4 + +[sub_resource type="CurveTexture" id="CurveTexture_qj75g"] +curve = SubResource("Curve_sx08r") + +[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_o8muh"] +particle_flag_disable_z = true +direction = Vector3(-1, 2.08165e-12, 2.08165e-12) +spread = 90.0 +initial_velocity_max = 16.0 +gravity = Vector3(2.08165e-12, 2.08165e-12, 2.08165e-12) +color = Color(1, 0.176471, 0.227451, 1) +hue_variation_min = -0.1 +hue_variation_max = 0.2 +hue_variation_curve = SubResource("CurveTexture_qj75g") + +[node name="Rocket" type="CharacterBody2D"] +motion_mode = 1 +script = ExtResource("1_tmk40") + +[node name="Sprite2D" type="Sprite2D" parent="."] +unique_name_in_owner = true + +[node name="SpriteHead" type="Sprite2D" parent="Sprite2D"] +texture = SubResource("AtlasTexture_admdg") +offset = Vector2(16, 2.08165e-12) + +[node name="SpriteEngine" type="Sprite2D" parent="Sprite2D"] +texture = SubResource("AtlasTexture_li1r2") +offset = Vector2(-16, 2.08165e-12) + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +rotation = 1.5708 +shape = SubResource("CapsuleShape2D_1uivg") + +[node name="Camera2D" type="Camera2D" parent="."] +unique_name_in_owner = true +zoom = Vector2(1.8, 1.8) + +[node name="GPUParticles2DMiddle" type="GPUParticles2D" parent="."] +position = Vector2(-11, 0) +amount = 16 +process_material = SubResource("ParticleProcessMaterial_0l0cx") + +[node name="GPUParticles2DTop" type="GPUParticles2D" parent="."] +position = Vector2(-11, -8) +amount = 16 +process_material = SubResource("ParticleProcessMaterial_o8muh") + +[node name="GPUParticles2DBottom" type="GPUParticles2D" parent="."] +position = Vector2(-11, 8) +amount = 16 +process_material = SubResource("ParticleProcessMaterial_o8muh") diff --git a/scenes/space.tscn b/scenes/space.tscn new file mode 100644 index 0000000..f4d83de --- /dev/null +++ b/scenes/space.tscn @@ -0,0 +1,26 @@ +[gd_scene load_steps=4 format=3 uid="uid://dbhsd2ggxhv5p"] + +[ext_resource type="PackedScene" uid="uid://doxwblduu0no0" path="res://scenes/rocket.tscn" id="1_y84a3"] +[ext_resource type="Script" path="res://scripts/AsteroidManager.gd" id="3_iv3c3"] +[ext_resource type="PackedScene" uid="uid://vkqp8mnf3jcx" path="res://scenes/space_gui.tscn" id="3_k1s8u"] + +[node name="Space" type="Node2D"] + +[node name="CanvasLayer2" type="CanvasLayer" parent="."] +layer = -1 + +[node name="Rocket" parent="." instance=ExtResource("1_y84a3")] +unique_name_in_owner = true + +[node name="AsteroidManager" type="Node2D" parent="."] +script = ExtResource("3_iv3c3") + +[node name="SpawnTimer" type="Timer" parent="AsteroidManager"] +unique_name_in_owner = true +wait_time = 0.2 +autostart = true + +[node name="SpaceGUI" parent="." instance=ExtResource("3_k1s8u")] +unique_name_in_owner = true + +[connection signal="timeout" from="AsteroidManager/SpawnTimer" to="AsteroidManager" method="_on_spawn_timer_timeout"] diff --git a/scenes/space_gui.tscn b/scenes/space_gui.tscn new file mode 100644 index 0000000..4a4218e --- /dev/null +++ b/scenes/space_gui.tscn @@ -0,0 +1,102 @@ +[gd_scene load_steps=6 format=3 uid="uid://vkqp8mnf3jcx"] + +[ext_resource type="Script" path="res://scripts/SpaceGUI.gd" id="1_pkcso"] +[ext_resource type="Texture2D" uid="uid://bdnapk5ubpkp5" path="res://assets/entities.png" id="2_ng047"] + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_2trj6"] +bg_color = Color(0.91722, 0.0430384, 0.16786, 1) + +[sub_resource type="AtlasTexture" id="AtlasTexture_c4ocm"] +atlas = ExtResource("2_ng047") +region = Rect2(160, 0, 32, 32) + +[sub_resource type="AtlasTexture" id="AtlasTexture_qq8k1"] +atlas = ExtResource("2_ng047") +region = Rect2(128, 0, 32, 32) + +[node name="SpaceGUI" type="CanvasLayer"] +script = ExtResource("1_pkcso") + +[node name="FuelGauge" type="ProgressBar" parent="."] +unique_name_in_owner = true +custom_minimum_size = Vector2(64, 2.08165e-12) +anchors_preset = 9 +anchor_bottom = 1.0 +offset_right = 64.0 +grow_vertical = 2 +theme_override_styles/fill = SubResource("StyleBoxFlat_2trj6") +value = 10.0 +fill_mode = 3 + +[node name="EjectPodLevel" type="HBoxContainer" parent="."] +unique_name_in_owner = true +custom_minimum_size = Vector2(128, 2.08165e-12) +offset_right = 128.0 +offset_bottom = 32.0 + +[node name="HSeparator" type="HSeparator" parent="EjectPodLevel"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 3.0 + +[node name="EjectIndicator" type="Control" parent="EjectPodLevel"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 + +[node name="Background" type="TextureRect" parent="EjectPodLevel/EjectIndicator"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = SubResource("AtlasTexture_c4ocm") + +[node name="EjectIcon" type="TextureRect" parent="EjectPodLevel/EjectIndicator"] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +texture = SubResource("AtlasTexture_qq8k1") +expand_mode = 3 + +[node name="PanelContainer" type="PanelContainer" parent="."] +anchors_preset = 3 +anchor_left = 1.0 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -40.0 +offset_top = -40.0 +grow_horizontal = 0 +grow_vertical = 0 + +[node name="MarginContainer" type="MarginContainer" parent="PanelContainer"] +layout_mode = 2 +theme_override_constants/margin_left = 4 +theme_override_constants/margin_top = 4 +theme_override_constants/margin_right = 4 +theme_override_constants/margin_bottom = 4 + +[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer/MarginContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 +theme_type_variation = &"HeaderSmall" +text = "Cargo:" + +[node name="CargoItemContainer" type="HBoxContainer" parent="PanelContainer/MarginContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="NoCargoPlaceholder" type="PanelContainer" parent="PanelContainer/MarginContainer/VBoxContainer/CargoItemContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="Label" type="Label" parent="PanelContainer/MarginContainer/VBoxContainer/CargoItemContainer/NoCargoPlaceholder"] +layout_mode = 2 +text = "None" diff --git a/scenes/space_pod.tscn b/scenes/space_pod.tscn new file mode 100644 index 0000000..100e263 --- /dev/null +++ b/scenes/space_pod.tscn @@ -0,0 +1,20 @@ +[gd_scene load_steps=5 format=3 uid="uid://bae2wqnjxk5we"] + +[ext_resource type="Script" path="res://scripts/space_pod.gd" id="1_mmqq1"] +[ext_resource type="Texture2D" uid="uid://bdnapk5ubpkp5" path="res://assets/entities.png" id="2_d0pm1"] + +[sub_resource type="AtlasTexture" id="AtlasTexture_t0d6u"] +atlas = ExtResource("2_d0pm1") +region = Rect2(128, 0, 32, 32) + +[sub_resource type="CircleShape2D" id="CircleShape2D_ydrnh"] +radius = 9.0 + +[node name="SpacePod" type="CharacterBody2D" groups=["pickups"]] +script = ExtResource("1_mmqq1") + +[node name="Sprite2D" type="Sprite2D" parent="."] +texture = SubResource("AtlasTexture_t0d6u") + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +shape = SubResource("CircleShape2D_ydrnh") diff --git a/scenes/star_fragment.tscn b/scenes/star_fragment.tscn new file mode 100644 index 0000000..2e74666 --- /dev/null +++ b/scenes/star_fragment.tscn @@ -0,0 +1,21 @@ +[gd_scene load_steps=5 format=3 uid="uid://syx7whg8ye4j"] + +[ext_resource type="Script" path="res://scripts/star_fragment.gd" id="1_l6qpy"] +[ext_resource type="Texture2D" uid="uid://bdnapk5ubpkp5" path="res://assets/entities.png" id="2_tqeu4"] + +[sub_resource type="AtlasTexture" id="AtlasTexture_n3jj8"] +atlas = ExtResource("2_tqeu4") +region = Rect2(96, 0, 32, 32) + +[sub_resource type="CircleShape2D" id="CircleShape2D_xku4r"] +radius = 12.0 + +[node name="StarFragment" type="CharacterBody2D" groups=["pickups"]] +script = ExtResource("1_l6qpy") + +[node name="Sprite2D" type="Sprite2D" parent="."] +texture = SubResource("AtlasTexture_n3jj8") + +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +position = Vector2(0, 1) +shape = SubResource("CircleShape2D_xku4r") diff --git a/scenes/upgrades_menu.tscn b/scenes/upgrades_menu.tscn new file mode 100644 index 0000000..1acc5ca --- /dev/null +++ b/scenes/upgrades_menu.tscn @@ -0,0 +1,104 @@ +[gd_scene load_steps=4 format=3 uid="uid://6ncdfxii2be8"] + +[ext_resource type="Theme" uid="uid://1pisbpunwphc" path="res://resources/launch_menu_theme.tres" id="1_d37q2"] +[ext_resource type="Script" path="res://scripts/upgrades_menu.gd" id="2_ilmj6"] +[ext_resource type="PackedScene" uid="uid://u13yhxooh61n" path="res://scenes/cost_container.tscn" id="6_3gxjk"] + +[node name="UpgradesMenu" type="Control"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +theme = ExtResource("1_d37q2") +script = ExtResource("2_ilmj6") + +[node name="MarginContainer" type="MarginContainer" parent="."] +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"] +layout_mode = 2 + +[node name="HeaderContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="ReturnButton" type="Button" parent="MarginContainer/VBoxContainer/HeaderContainer"] +layout_mode = 2 +text = "Return +" + +[node name="UpgradesLabel" type="Label" parent="MarginContainer/VBoxContainer/HeaderContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_type_variation = &"HeaderMedium" +text = "Upgrades:" +horizontal_alignment = 1 + +[node name="HSeparator" type="HSeparator" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="MainContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer"] +layout_mode = 2 +size_flags_vertical = 3 + +[node name="UpgradesList" type="ItemList" parent="MarginContainer/VBoxContainer/MainContainer"] +unique_name_in_owner = true +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +item_count = 2 +item_0/text = "Upgrade 1" +item_1/text = "Upgrade 2" + +[node name="PanelContainer" type="PanelContainer" parent="MarginContainer/VBoxContainer/MainContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/VBoxContainer/MainContainer/PanelContainer"] +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/MainContainer/PanelContainer/MarginContainer"] +layout_mode = 2 + +[node name="UpgradeTitleLabel" type="Label" parent="MarginContainer/VBoxContainer/MainContainer/PanelContainer/MarginContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +theme_type_variation = &"HeaderSmall" +text = "UpgradeTitle" + +[node name="HSeparator" type="HSeparator" parent="MarginContainer/VBoxContainer/MainContainer/PanelContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 + +[node name="DesciptionLabel" type="Label" parent="MarginContainer/VBoxContainer/MainContainer/PanelContainer/MarginContainer/VBoxContainer"] +unique_name_in_owner = true +custom_minimum_size = Vector2(100, 64) +layout_mode = 2 +size_flags_vertical = 3 +text = "Description Text For an upgrade that is super long and should auto-wrap to the next line that describes what is happening with the upgrade." +autowrap_mode = 3 + +[node name="CostLabel" type="Label" parent="MarginContainer/VBoxContainer/MainContainer/PanelContainer/MarginContainer/VBoxContainer"] +layout_mode = 2 +text = "Cost:" + +[node name="CostContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer/MainContainer/PanelContainer/MarginContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="CostContainer" parent="MarginContainer/VBoxContainer/MainContainer/PanelContainer/MarginContainer/VBoxContainer/CostContainer" instance=ExtResource("6_3gxjk")] +layout_mode = 2 + +[node name="BuyButton" type="Button" parent="MarginContainer/VBoxContainer/MainContainer/PanelContainer/MarginContainer/VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "Buy" + +[connection signal="item_selected" from="MarginContainer/VBoxContainer/MainContainer/UpgradesList" to="." method="_on_upgrades_list_item_selected"] +[connection signal="pressed" from="MarginContainer/VBoxContainer/MainContainer/PanelContainer/MarginContainer/VBoxContainer/BuyButton" to="." method="_on_buy_button_pressed"] diff --git a/scripts/AsteroidManager.gd b/scripts/AsteroidManager.gd new file mode 100644 index 0000000..88f993a --- /dev/null +++ b/scripts/AsteroidManager.gd @@ -0,0 +1,66 @@ +extends Node2D + +const ASTEROID = preload("res://scenes/asteroid.tscn") +const FUEL_TANK = preload("res://scenes/fuel_tank.tscn") +const STAR_FRAGMENT = preload("res://scenes/star_fragment.tscn") + +@onready var rocket: Rocket = %Rocket +@onready var spawn_timer = %SpawnTimer + +var noise: Noise +@export var delim_value: float = 0.34 +@export var fuel_value: float = 0.3#-0.58 +@export var star_rarity: float = 0.5#0.1 + +func _ready(): + noise = FastNoiseLite.new() + noise.seed = randi() % 1000000 + noise.frequency = 0.007 + + for pod: SpacePod in DataHandler.spawn_pods(): + add_child(pod) + + +func _on_spawn_timer_timeout(): + spawn_timer.wait_time = 9.5 / max(rocket.velocity.length(), 8) + for a in range(-36, 36): + var angle = rocket.rotation + (a / 30.0) + (randf() / 25.0) + var pos = rocket.position + (Vector2.from_angle(angle) * 420.0) + + var value = noise.get_noise_2d(pos.x, pos.y) + if value > delim_value: + if _check_spawn_loc(pos, 13): + var asteroid_spawn = ASTEROID.instantiate() + asteroid_spawn.position = pos + add_child(asteroid_spawn) + + if value < fuel_value: + var rand_pickup = randf() + if _check_spawn_loc(pos, 82): + if rand_pickup < star_rarity: + var star = STAR_FRAGMENT.instantiate() + star.position = pos + add_child(star) + else: + var fuel_spawn = FUEL_TANK.instantiate() + fuel_spawn.position = pos + add_child(fuel_spawn) + +func _check_spawn_loc(pos: Vector2, radius: int): + var shape_rid = PhysicsServer2D.circle_shape_create() + + PhysicsServer2D.shape_set_data(shape_rid, radius) + + var params = PhysicsShapeQueryParameters2D.new() + params.shape_rid = shape_rid + params.transform = Transform2D(0.0, pos) + params.collision_mask = 1 + + # Execute physics queries here... + var space_state = get_world_2d().direct_space_state + var results = space_state.intersect_shape(params) + + # Release the shape when done with physics queries. + PhysicsServer2D.free_rid(shape_rid) + + return results.is_empty() diff --git a/scripts/SpaceGUI.gd b/scripts/SpaceGUI.gd new file mode 100644 index 0000000..a714fc4 --- /dev/null +++ b/scripts/SpaceGUI.gd @@ -0,0 +1,42 @@ +extends CanvasLayer + +@onready var fuel_gauge = %FuelGauge +@onready var eject_pod_level = %EjectPodLevel +@onready var eject_indicator_bkgd := $EjectPodLevel/EjectIndicator/Background +@onready var cargo_item_container = %CargoItemContainer +@onready var no_cargo_placeholder = %NoCargoPlaceholder + +const CARGO_ITEM = preload("res://scenes/cargo_item.tscn") + +var cargo_gui_panels := {} + +func _ready(): + DataHandler.pickup_item.connect(_on_pickup_item) + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _process(delta): + fuel_gauge.value = DataHandler.get_fuel() + var percent = DataHandler.get_fuel_percent() + var r = min(2 - 2 * percent, 1) + var g = min(2.0 * percent, 1) + fuel_gauge.get("theme_override_styles/fill").bg_color = Color(r, g, 0.0) + + var eject_percent = DataHandler.eject_fuel_max_percentage() + eject_pod_level.position.y = (1.0 - eject_percent) * get_viewport().size.y - 16 + eject_indicator_bkgd.modulate = Color.GREEN if DataHandler.has_fuel_needed_for_eject() else Color.RED + +func _on_pickup_item(): + if DataHandler.has_cargo(): + if cargo_gui_panels.is_empty(): + no_cargo_placeholder.queue_free() + for key in DataHandler.game_data.cargo: + var count = DataHandler.game_data.cargo[key] + if count > 0: + if not cargo_gui_panels.has(key): + cargo_gui_panels[key] = CARGO_ITEM.instantiate() + cargo_item_container.add_child(cargo_gui_panels[key]) + cargo_gui_panels[key].init(key, count) + else: + cargo_gui_panels[key].update_count(count) + + var cargo_gui: Node diff --git a/scripts/cargo_item.gd b/scripts/cargo_item.gd new file mode 100644 index 0000000..fad037f --- /dev/null +++ b/scripts/cargo_item.gd @@ -0,0 +1,12 @@ +extends PanelContainer +class_name CargoItem + +@onready var item_texture: TextureRect = %ItemTexture +@onready var item_label: Label = %ItemLabel + +func init(type: String, count: int): + item_texture.texture = DataHandler.type_to_texture(type) + item_label.text = "x%s" % count + +func update_count(count: int): + item_label.text = "x%s" % count diff --git a/scripts/configuration_menu.gd b/scripts/configuration_menu.gd new file mode 100644 index 0000000..88929b4 --- /dev/null +++ b/scripts/configuration_menu.gd @@ -0,0 +1,16 @@ +extends Control +class_name ConfigurationMenu + +@onready var modules_container = %ModulesContainer + +const MODULE_SELECTOR = preload("res://scenes/module_selector.tscn") + +func _ready() -> void: + for child in modules_container.get_children(): + modules_container.remove_child(child) + modules_container.add_spacer(true) + modules_container.add_child(MODULE_SELECTOR.instantiate().with_location_data(GameData.ModuleType.ENGINE)) + for i in DataHandler.get_mid_count(): + modules_container.add_child(MODULE_SELECTOR.instantiate().with_location_data(GameData.ModuleType.MID, i)) + modules_container.add_child(MODULE_SELECTOR.instantiate().with_location_data(GameData.ModuleType.COCKPIT)) + modules_container.add_spacer(false) diff --git a/scripts/cost_container.gd b/scripts/cost_container.gd new file mode 100644 index 0000000..18d5d94 --- /dev/null +++ b/scripts/cost_container.gd @@ -0,0 +1,9 @@ +extends BoxContainer +class_name CostContainer + +@onready var resource_texture := $HBoxContainer/ResourceTexture +@onready var cost_text_edit := $HBoxContainer/CostTextEdit + +func set_data(tex: Texture, cost: int) -> void: + resource_texture.texture = tex + cost_text_edit.text = str(cost) diff --git a/scripts/data_handler.gd b/scripts/data_handler.gd new file mode 100644 index 0000000..47ab018 --- /dev/null +++ b/scripts/data_handler.gd @@ -0,0 +1,135 @@ +extends Node + +signal pickup_item + +const ENTITIES = preload("res://assets/entities.png") +const SAVE_FILE = "user://misguided_data.tres" +const SPACE_POD = preload("res://scenes/space_pod.tscn") + +const type_to_int ={ + "star": 96, + "fuel": 64 +} + +var game_data: GameData + +var current_rocket: Rocket + +func get_current_rocket_display() -> Array[TextureRect]: + var rocket: Array[TextureRect] = [] + return rocket + +func ready_to_launch() -> bool: + return not game_data.modules[GameData.ModuleType.ENGINE].is_empty() and not game_data.modules[GameData.ModuleType.COCKPIT] + +func launch_rocket() -> void: + game_data.cargo = {} + game_data.fuel = game_data.fuel_max + +func pickup(type: String) -> void: + game_data.pickup(type) + pickup_item.emit() + +func has_cargo() -> bool: + return game_data.has_cargo() + +func type_to_texture(type: String) -> Texture: + var texture := AtlasTexture.new() + texture.atlas = ENTITIES + texture.region = Rect2(type_to_int[type], 0, 32, 32) + return texture + +func move_cargo_to_inventory() -> void: + game_data.move_cargo_to_inventory() + +func create_cargo_pod(pod_position: Vector2) -> void: + game_data.create_cargo_pod(pod_position) + +func save_game_data() -> void: + ResourceSaver.save(game_data, SAVE_FILE) + +func load_game_data() -> void: + if ResourceLoader.exists(SAVE_FILE): + game_data = load(SAVE_FILE) + else: + game_data = GameData.new() + +func spawn_pods() -> Array[SpacePod]: + var pod_nodes: Array[SpacePod] = [] + for pod: FloatingPodData in game_data.floating_pods: + var pod_spawn = SPACE_POD.instantiate() + pod_spawn.pod_data = pod + pod_spawn.position = pod.position + pod_nodes.append(pod_spawn) + return pod_nodes + +func retrive_pod(pod_data: FloatingPodData) -> void: + for item in pod_data.cargo: + game_data.pickup(item, pod_data.cargo[item]) + var index = game_data.floating_pods.find(pod_data) + if index >= 0: + game_data.floating_pods.remove_at(index) + pickup_item.emit() + +func remove_module(location: GameData.ModuleType, part: String) -> void: + print("Module removed %s" % part) + +func add_module(location: GameData.ModuleType, part: Module) -> void: + print("Module added %s" % part) + +func initial_velocity() -> Vector2: + return Vector2.RIGHT * get_speed() * 0.011 + +func has_fuel() -> bool: + return game_data.fuel > 0 + +func add_fuel(amount: float, apply_efficiency: bool = false) -> void: + game_data.fuel += amount if not apply_efficiency else amount * game_data.fuel_efficiency + +func get_fuel() -> float: + return game_data.fuel + +func get_fuel_percent() -> float: + return game_data.fuel / game_data.fuel_max + +func get_speed() -> float: + return game_data.SPEED + +func get_maneuverability() -> float: + return 0.01 + +func has_fuel_needed_for_eject() -> bool: + return eject_fuel_percentage() >= 1.0 + +func eject_fuel_percentage() -> float: + return game_data.fuel / fuel_needed_for_eject() + +func eject_fuel_max_percentage() -> float: + return fuel_needed_for_eject() / game_data.fuel_max + +func fuel_needed_for_eject() -> float: + return max(current_rocket.position.length() / 160.0, 10.0) + +func get_available_upgrades() -> Array[Upgrade]: + return game_data.upgrades.filter(func(upgd: Upgrade) -> bool: return not upgd.purchased) + +func purchase_upgrade(upgrade: Upgrade) -> bool: + upgrade.purchased = not upgrade.purchased + if not has_upgrade_cost(upgrade): return false + var index = game_data.upgrades.find(upgrade) + if index < 0: return false + #TODO finsh this + print(game_data.upgrades[index].purchased) + return true + +func has_upgrade_cost(upgrade: Upgrade) -> bool: + for item in upgrade.cost: + if not game_data.inventory.has(item) or game_data.inventory[item] < upgrade.cost[item]: + return false + return true + +func get_modules_for_location(location: GameData.ModuleType) -> Array: + return game_data.modules[location] + +func get_mid_count() -> int: + return game_data.mid_module_limit diff --git a/scripts/floating_pod_data.gd b/scripts/floating_pod_data.gd new file mode 100644 index 0000000..8d19910 --- /dev/null +++ b/scripts/floating_pod_data.gd @@ -0,0 +1,5 @@ +extends Resource +class_name FloatingPodData + +@export var position: Vector2 +@export var cargo: Dictionary = {} diff --git a/scripts/fuel_tank.gd b/scripts/fuel_tank.gd new file mode 100644 index 0000000..7993f14 --- /dev/null +++ b/scripts/fuel_tank.gd @@ -0,0 +1,7 @@ +extends Pickup +class_name FuelTank + +func pickup(rocket: Rocket): + if not is_queued_for_deletion(): + DataHandler.pickup('fuel') + super(rocket) diff --git a/scripts/game_data.gd b/scripts/game_data.gd new file mode 100644 index 0000000..dc2994b --- /dev/null +++ b/scripts/game_data.gd @@ -0,0 +1,58 @@ +class_name GameData +extends Resource + +@export var SPEED: float = 2900.0 +@export var ENGINE_SPEED: float = 1900.0 +@export var fuel: float = 100 +@export var fuel_max: float = 100 +@export var engine_speed_modifier: float = 1.0 +@export var fuel_efficiency: float = 1.0 +@export var engine_maneuverability_modifier: float = 1.0 + +@export var cargo: Dictionary = {} +@export var inventory: Dictionary = {} +@export var upgrades: Array[Upgrade] = [FirstUpgrade.new()] +@export var mid_module_limit: int = 0 +@export var modules: Dictionary = { + 0: [], + 1: [], + 2: [] +} +@export var floating_pods: Array[FloatingPodData] = [] + +enum ModuleType { + ENGINE, + MID, + COCKPIT +} + +func get_active_rocket_modules() -> Array[Module]: + var _modules: Array[Module] = [] + return _modules + +static func get_module_type_name(_type: ModuleType) -> String: + return ModuleType.keys()[_type] + +func pickup(type: String, amount: int = 1) -> void: + if not cargo.has(type): + cargo[type] = 0 + cargo[type] += amount + +func has_cargo() -> bool: + var cargo_size: int = 0 + for item: int in cargo.values(): + cargo_size += item + return cargo_size > 0 + +func move_cargo_to_inventory() -> void: + for item: String in cargo: + if not inventory.has(item): + inventory[item] = 0 + inventory[item] += cargo[item] + +func create_cargo_pod(pod_position: Vector2) -> void: + if not cargo.is_empty(): + var pod: FloatingPodData = FloatingPodData.new() + pod.position = pod_position + pod.cargo = cargo + floating_pods.append(pod) diff --git a/scripts/inventory_menu.gd b/scripts/inventory_menu.gd new file mode 100644 index 0000000..f8d02f9 --- /dev/null +++ b/scripts/inventory_menu.gd @@ -0,0 +1,14 @@ +extends Control + +const CARGO_ITEM = preload("res://scenes/cargo_item.tscn") + +@onready var inventory_container = %InventoryContainer + +func _ready(): + for child in inventory_container.get_children(): + inventory_container.remove_child(child) + for item in DataHandler.game_data.inventory: + var grid_item = CARGO_ITEM.instantiate() + inventory_container.add_child(grid_item) + grid_item.init(item, DataHandler.game_data.inventory[item]) + diff --git a/scripts/launch_menu.gd b/scripts/launch_menu.gd new file mode 100644 index 0000000..116e3e7 --- /dev/null +++ b/scripts/launch_menu.gd @@ -0,0 +1,58 @@ +extends Control + +const UPGRADES_MENU = preload("res://scenes/upgrades_menu.tscn") +const CONFIGURATION_MENU = preload("res://scenes/configuration_menu.tscn") +const INVENTORY_MENU = preload("res://scenes/inventory_menu.tscn") + +@onready var menu_root = %MenuRoot +@onready var rocket_display_container = %RocketDisplayContainer +@onready var configuration_button = $MenuRoot/RightMenu/ButtonContainer/ConfigurationButton +@onready var launch_button = $MenuRoot/RightMenu/ButtonContainer/LaunchButton + +var upgrades_menu: Node +var configuration_menu: Node +var inventory_menu: Node + +func _ready(): + upgrades_menu = UPGRADES_MENU.instantiate() + upgrades_menu.get_node("MarginContainer/VBoxContainer/HeaderContainer/ReturnButton").connect("pressed", _on_upgrades_return_button_pressed) + + configuration_menu = CONFIGURATION_MENU.instantiate() + configuration_menu.get_node("MarginContainer/VBoxContainer/HBoxContainer/ReturnButton").connect("pressed", _on_configuration_return_button_pressed) + + inventory_menu = INVENTORY_MENU.instantiate() + inventory_menu.get_node("MarginContainer/VBoxContainer/HBoxContainer/ReturnButton").connect("pressed", _on_inventory_return_button_pressed) + + DataHandler.load_game_data() + + if not DataHandler.ready_to_launch(): + configuration_button.disabled = true + launch_button.disabled = true + +func _on_launch_button_pressed(): + DataHandler.launch_rocket() + get_tree().change_scene_to_file("res://scenes/space.tscn") + +func _on_upgrades_return_button_pressed(): + remove_child(upgrades_menu) + menu_root.visible = true + +func _on_configuration_return_button_pressed(): + remove_child(configuration_menu) + menu_root.visible = true + +func _on_inventory_return_button_pressed(): + remove_child(inventory_menu) + menu_root.visible = true + +func _on_upgrades_button_pressed(): + add_child(upgrades_menu) + menu_root.visible = false + +func _on_configuration_button_pressed(): + add_child(configuration_menu) + menu_root.visible = false + +func _on_inventory_button_pressed(): + add_child(inventory_menu) + menu_root.visible = false diff --git a/scripts/module_selector.gd b/scripts/module_selector.gd new file mode 100644 index 0000000..6a66050 --- /dev/null +++ b/scripts/module_selector.gd @@ -0,0 +1,18 @@ +extends VBoxContainer +class_name ModuleSelector + +@onready var location_label = %LocationLabel +@onready var module_selector_option = %ModuleSelectorOption +@onready var module_texture = %ModuleTexture +@onready var description_label = %DescriptionLabel + +var _location: GameData.ModuleType +var modules_list: Array + +func with_location_data(location: GameData.ModuleType, _mid_index: int = 0) -> ModuleSelector: + _location = location + return self + +func _ready(): + modules_list = DataHandler.get_modules_for_location(_location) + location_label.text = GameData.get_module_type_name(_location) diff --git a/scripts/modules/module.gd b/scripts/modules/module.gd new file mode 100644 index 0000000..1278156 --- /dev/null +++ b/scripts/modules/module.gd @@ -0,0 +1,13 @@ +extends Resource +class_name Module + +@export var name: String = "Base Module" +@export var description: String = "Base Module Description" +@export var texture: Texture +@export var tier: int = -1 +@export var location: GameData.ModuleType +@export var active: int = -1 + +@export var base_fuel_max: int = 10 +@export var base_speed: float = 2900.0 +@export var base_maneuverability: float = 0.01 diff --git a/scripts/modules/standard_cockpit_module.gd b/scripts/modules/standard_cockpit_module.gd new file mode 100644 index 0000000..e0ed0fa --- /dev/null +++ b/scripts/modules/standard_cockpit_module.gd @@ -0,0 +1,6 @@ +extends Module +class_name StandardCockpitModule + +func _init(): + name = "Standard Cockpit" + location = GameData.ModuleType.COCKPIT diff --git a/scripts/modules/standard_engine_module.gd b/scripts/modules/standard_engine_module.gd new file mode 100644 index 0000000..5dff82a --- /dev/null +++ b/scripts/modules/standard_engine_module.gd @@ -0,0 +1,6 @@ +extends Module +class_name StandardEngineModule + +func _init(): + name = "Standard Engine" + location = GameData.ModuleType.ENGINE diff --git a/scripts/pickup.gd b/scripts/pickup.gd new file mode 100644 index 0000000..f99a23b --- /dev/null +++ b/scripts/pickup.gd @@ -0,0 +1,6 @@ +extends CharacterBody2D +class_name Pickup + +func pickup(rocket: Rocket): + if not is_queued_for_deletion(): + queue_free() diff --git a/scripts/rocket.gd b/scripts/rocket.gd new file mode 100644 index 0000000..866aab2 --- /dev/null +++ b/scripts/rocket.gd @@ -0,0 +1,74 @@ +extends CharacterBody2D +class_name Rocket + +@onready var camera := %Camera2D +@onready var sprite := %Sprite2D +@onready var gpu_particles_2d_middle := $GPUParticles2DMiddle +@onready var gpu_particles_2d_top := $GPUParticles2DTop +@onready var gpu_particles_2d_bottom := $GPUParticles2DBottom + +var is_ejecting = false + +func _ready() -> void: + DataHandler.current_rocket = self + #velocity = DataHandler.initial_velocity() + +# Called every frame. 'delta' is the elapsed time since the previous frame. +func _physics_process(delta: float) -> void: + if Input.is_action_just_pressed("eject"): + _handle_eject() + + var move_stick: Vector2 = Input.get_vector("move_left", "move_right", "move_up", "move_down") + + if DataHandler.has_fuel() and move_stick: + var new_velocity := velocity.lerp(move_stick * delta * DataHandler.get_speed(), DataHandler.get_maneuverability()) + var velocity_change := velocity - new_velocity + velocity = new_velocity + DataHandler.add_fuel(velocity_change.length() / -5.0, true) + rotation = velocity.angle() + _set_particles(velocity_change.length()) + else: + _stop_particles() + + var collisions := move_and_slide() + if collisions: + for i in get_slide_collision_count(): + var collision := get_slide_collision(i) + var collider := collision.get_collider() + if collider.is_in_group("pickups"): + collider.pickup(self) + else: + if DataHandler.has_fuel(): + DataHandler.add_fuel(-5 * delta) + else: + _handle_eject() + + + #fuel -= velocity.length() / 900.0 + +func _stop_particles() -> void: + gpu_particles_2d_bottom.emitting = false + gpu_particles_2d_middle.emitting = false + gpu_particles_2d_top.emitting = false + +func _set_particles(intensity: float) -> void: + gpu_particles_2d_bottom.emitting = true + gpu_particles_2d_middle.emitting = true + gpu_particles_2d_top.emitting = true + + gpu_particles_2d_bottom.lifetime = 5.0 * intensity + gpu_particles_2d_middle.lifetime = 6.0 * intensity + gpu_particles_2d_top.lifetime = 5.0 * intensity + +func _handle_eject() -> void: + if not is_ejecting: + is_ejecting = true + if DataHandler.has_fuel_needed_for_eject(): + DataHandler.move_cargo_to_inventory() + else: + var eject_percent = DataHandler.eject_fuel_percentage() + var pod_position = position * (1 - eject_percent) + DataHandler.create_cargo_pod(pod_position) + + DataHandler.save_game_data() + get_tree().change_scene_to_file("res://scenes/launch_menu.tscn") diff --git a/scripts/rocket_texture.gd b/scripts/rocket_texture.gd new file mode 100644 index 0000000..78119d4 --- /dev/null +++ b/scripts/rocket_texture.gd @@ -0,0 +1,15 @@ +extends CanvasItem +class_name RocketTexture + +@export var cockpit_tex: Texture +@export var engine_tex: Texture + +func _draw(): + cockpit_tex.draw() + engine_tex.draw() + +func _get_height(): + return cockpit_tex.get_height() + +func _get_width(): + return cockpit_tex.get_width() + engine_tex.get_width() diff --git a/scripts/space_pod.gd b/scripts/space_pod.gd new file mode 100644 index 0000000..b375076 --- /dev/null +++ b/scripts/space_pod.gd @@ -0,0 +1,9 @@ +extends Pickup +class_name SpacePod + +var pod_data: FloatingPodData + +func pickup(rocket: Rocket): + if not is_queued_for_deletion(): + DataHandler.retrive_pod(pod_data) + super(rocket) diff --git a/scripts/star_fragment.gd b/scripts/star_fragment.gd new file mode 100644 index 0000000..5ef7ec8 --- /dev/null +++ b/scripts/star_fragment.gd @@ -0,0 +1,7 @@ +extends Pickup +class_name StarFragment + +func pickup(rocket: Rocket): + if not is_queued_for_deletion(): + DataHandler.pickup("star") + super(rocket) diff --git a/scripts/upgrades/engine_maneuverability_upgrade.gd b/scripts/upgrades/engine_maneuverability_upgrade.gd new file mode 100644 index 0000000..7e9f844 --- /dev/null +++ b/scripts/upgrades/engine_maneuverability_upgrade.gd @@ -0,0 +1,19 @@ +extends Upgrade +class_name EngineManeuverabilityUpgrade + +@export var modifier := 1.8 + +func _init(tier: int = 1): + if tier < 5: + unlocks.append(EngineManeuverabilityUpgrade.new(tier + 1)) + self.tier = tier + name = "Engine Maneuverability Increase - Tier %s" % ['I','II','III','IV','V'][tier - 1] + description = "Increases the turning speed of your rocket" + cost = { + "star": (tier) * 15 - 5 + } + +func activate_upgrade() -> bool: + if not super(): return false + DataHandler.game_data.engine_maneuverability_modifier *= modifier + return true diff --git a/scripts/upgrades/engine_speed_upgrade.gd b/scripts/upgrades/engine_speed_upgrade.gd new file mode 100644 index 0000000..ad384be --- /dev/null +++ b/scripts/upgrades/engine_speed_upgrade.gd @@ -0,0 +1,20 @@ +extends Upgrade +class_name EngineSpeedUpgrade + +@export var modifier := 1.2 + +func _init(tier: int = 1): + if tier < 5: + unlocks.append(EngineSpeedUpgrade.new(tier + 1)) + self.tier = tier + name = "Engine Speed Increase - Tier %s" % ['I','II','III','IV','V'][tier - 1] + description = "Increases the top speed of your rocket" + cost = { + "fuel": (tier) * 10 - 5, + "star": tier * 5 + 10 + } + +func activate_upgrade() -> bool: + if not super(): return false + DataHandler.game_data.engine_speed_modifier *= modifier + return true diff --git a/scripts/upgrades/first_upgrade.gd b/scripts/upgrades/first_upgrade.gd new file mode 100644 index 0000000..2fba12d --- /dev/null +++ b/scripts/upgrades/first_upgrade.gd @@ -0,0 +1,8 @@ +extends Upgrade +class_name FirstUpgrade + +func _init(): + name = "First Upgrade" + description = "Buy this upgrade to unlock the six initial upgrades" + cost = {} + unlocks = [StandardCockpitUpgrade.new(), StandardEngineUpgrade.new(), FuelEfficiencyUpgrade.new(), FuelCapacityUpgrade.new(), EngineSpeedUpgrade.new(), EngineManeuverabilityUpgrade.new()] diff --git a/scripts/upgrades/fuel_capacity_upgrade.gd b/scripts/upgrades/fuel_capacity_upgrade.gd new file mode 100644 index 0000000..be7c53a --- /dev/null +++ b/scripts/upgrades/fuel_capacity_upgrade.gd @@ -0,0 +1,19 @@ +extends Upgrade +class_name FuelCapacityUpgrade + +@export var size_increase := 40 + +func _init(tier: int = 1): + if tier < 5: + unlocks.append(FuelCapacityUpgrade.new(tier + 1)) + self.tier = tier + name = "Fuel Capacity Increase - Tier %s" % ['I','II','III','IV','V'][tier - 1] + description = "Increases the maximum amount of fuel your rocket can hold" + cost = { + "fuel": (tier) * 20 - 5 + } + +func activate_upgrade() -> bool: + if not super(): return false + DataHandler.game_data.fuel_max += size_increase + return true diff --git a/scripts/upgrades/fuel_efficiency_upgrade.gd b/scripts/upgrades/fuel_efficiency_upgrade.gd new file mode 100644 index 0000000..92f009e --- /dev/null +++ b/scripts/upgrades/fuel_efficiency_upgrade.gd @@ -0,0 +1,20 @@ +extends Upgrade +class_name FuelEfficiencyUpgrade + +@export var modifier := 0.95 + +func _init(tier: int = 1): + if tier < 5: + unlocks.append(FuelEfficiencyUpgrade.new(tier + 1)) + self.tier = tier + name = "Fuel Efficiency Increase - Tier %s" % ['I','II','III','IV','V'][tier - 1] + description = "Increases the efficiency of fuel use for your rocket" + cost = { + "fuel": (tier) * 15 - 5, + "star": (tier) * 5 + 5 + } + +func activate_upgrade() -> bool: + if not super(): return false + DataHandler.game_data.fuel_efficiency *= modifier + return true diff --git a/scripts/upgrades/standard_cockpit_upgrade.gd b/scripts/upgrades/standard_cockpit_upgrade.gd new file mode 100644 index 0000000..3c7f799 --- /dev/null +++ b/scripts/upgrades/standard_cockpit_upgrade.gd @@ -0,0 +1,25 @@ +extends Upgrade +class_name StandardCockpitUpgrade + +var module: StandardCockpitModule + +func _init(tier: int = 1, module: StandardCockpitModule = null): + if module == null: + self.module = StandardCockpitModule.new() + else: + self.module = module + if tier < 5: + unlocks.append(StandardCockpitUpgrade.new(tier + 1, module)) + self.tier = tier + name = "Standard Cockpit Tier %s" % ['I','II','III','IV','V'][tier - 1] + description = "Standard Cockpit with no specialties, no strengths, no weakness." + cost = { + "star": (tier - 1) * 10 + } + +func activate_upgrade() -> bool: + if not super(): return false + if tier == 1: + DataHandler.add_module(GameData.ModuleType.COCKPIT, module) + module.tier = tier + return true diff --git a/scripts/upgrades/standard_engine_upgrade.gd b/scripts/upgrades/standard_engine_upgrade.gd new file mode 100644 index 0000000..ac115b6 --- /dev/null +++ b/scripts/upgrades/standard_engine_upgrade.gd @@ -0,0 +1,25 @@ +extends Upgrade +class_name StandardEngineUpgrade + +var module: StandardEngineModule + +func _init(tier: int = 1, module: StandardEngineModule = null): + if module == null: + self.module = StandardEngineModule.new() + else: + self.module = module + if tier < 5: + unlocks.append(StandardEngineUpgrade.new(tier + 1)) + self.tier = tier + name = "Standard Engine Tier %s" % ['I','II','III','IV','V'][tier - 1] + description = "Standard Engine with no specialties, all around engine" + cost = { + "star": (tier - 1) * 10 + } + +func activate_upgrade() -> bool: + if not super(): return false + if tier == 1: + DataHandler.add_module(GameData.ModuleType.ENGINE, module) + module.tier = tier + return true diff --git a/scripts/upgrades/upgrade.gd b/scripts/upgrades/upgrade.gd new file mode 100644 index 0000000..de15382 --- /dev/null +++ b/scripts/upgrades/upgrade.gd @@ -0,0 +1,17 @@ +extends Resource +class_name Upgrade + +@export var name: String = "Base Upgrade" +@export var description: String = "Base Description" +@export var unlocks: Array[Upgrade] +@export var cost: Dictionary = { + "star": 1 +} +@export var purchased: bool = false +@export var tier: int = -1 + +func activate_upgrade() -> bool: + if DataHandler.has_upgrade_cost(self): return false + purchased = true + DataHandler.game_data.upgrades.append_array(unlocks) + return true diff --git a/scripts/upgrades_menu.gd b/scripts/upgrades_menu.gd new file mode 100644 index 0000000..d433a53 --- /dev/null +++ b/scripts/upgrades_menu.gd @@ -0,0 +1,43 @@ +extends Control + +const COST_CONTAINER = preload("res://scenes/cost_container.tscn") + +@onready var upgrades_list = %UpgradesList +@onready var upgrade_title_label = %UpgradeTitleLabel +@onready var desciption_label = %DesciptionLabel +@onready var cost_container = %CostContainer +@onready var buy_button = %BuyButton + +var available_upgrades: Array[Upgrade] + +func _ready(): + upgrade_title_label.text = "<- Select an Upgrade" + desciption_label.text = "" + for child in cost_container.get_children(): + cost_container.remove_child(child) + buy_button.disabled = true + upgrades_list.clear() + available_upgrades = DataHandler.get_available_upgrades() + for upgd in available_upgrades: + upgrades_list.add_item(upgd.name) + + +func _on_upgrades_list_item_selected(index): + var upgd: Upgrade = available_upgrades[index] + upgrade_title_label.text = upgd.name + desciption_label.text = upgd.description + for child in cost_container.get_children(): + cost_container.remove_child(child) + for cost in upgd.cost: + var cost_row: CostContainer = COST_CONTAINER.instantiate() + cost_container.add_child(cost_row) + cost_row.set_data(DataHandler.type_to_texture(cost), upgd.cost[cost]) + if DataHandler.has_upgrade_cost(upgd): + buy_button.disabled = false + + +func _on_buy_button_pressed(): + var selected = upgrades_list.get_selected_items()[0] + var upgd: Upgrade = available_upgrades[selected] + if upgd.activate_upgrade(): + _ready()