From 87c52d85c0859cf46c3aa17688efe9503986b321 Mon Sep 17 00:00:00 2001 From: Paolo Pustorino Date: Thu, 28 Dec 2023 16:15:04 +0100 Subject: [PATCH] refs #37: Rooms Aseprite importer preparatory code and PopochiuObjects creation refactoring (#64) * refs #37: Squashed Room Importer and Objects creation refactoring. refs #37: Preparation for refactoring of importer code. refs #37: The plugin is now ready for the implementation of the Room importing logic. refs #37: WIP refactoring the prop creation logic. It works but needs heavy polishing. refs #37: Open #67 about the heavy polishing, in the meantime this reduce the passing-over of UI elements. refs #37: Changed commit language. refs #37: Refactored the region creation following the new template. Added a base class to limit code-duplication. refs #37: Refactored the walkable areas too. refs #37: Refactored the hotspots creation too. refs #37: Project file updated to Godot 4.1 refs #37: Rooms creation have been refactored. refs #37: Characters creation have been refactored. refs #37: Inventory items creation have been refactored. refs #37: Dialog creation have been refactored. refs #37: some code dedup on type helpers. refs #37: code dedup in helpers should be over. Also addressed annoying Godot 4.1 bug. refs #37: all creation helpers have been renamed to factories. refs #37: factories init methods changed to real constructors. refs #37: renamed and added internal factories variables for better readability. refs #37: Fixed inconsistency in inventory items file naming. refs #37: Renamed local variables in factory classes. refs #37: Switch to a success state return strategy to bubble feedback up to the popup. refs #37: Fixed wrong ownership for room objects children bug. refs #37: Cleaned up useless and hard-to-maintain references to the obj types in comments. Addresses PR comments by @carenalga. refs #37: Renamed factories private members to reduce unnecessary verbosity. refs #37: Solved error message about invalid ownership when creating room objects. refs #37: WIP for rooms importer. This code is broken! refs #37: WIP for rooms importer. This code is broken! refs #37: WIP for rooms importer. This code is broken! refs #37: A lot of code rewriting to make the base feature work. It works :) refs #37: More work to get rid of strange execution errors. refs #37: moving stuff in a desperate attempt to make this working in a predictable way. refs #37: changed the importing flow strategy to circumvent a nasty error on linux with filesystem signals availability. refs #37: off-by-one error fixed in animation frames range. refs #37: fixed an error I probably inherited from another branch. TEST1: messing with importer. refs #37: worked around the console error when packing a scene with not-owned children (please see Godot GH-81982 for details). refs #37: visibility toggle in the importer interface is now working as intended. refs #37: all props now have an attached script, even the non-clickable ones; the importer is now able to update visibility and clickability status of already imported props. refs #37: InteractionPolygons for props and hotspots have been moved in the scene and an edit button has been exposed in the toolbar to edit them. Fix missing images when importing room from Aseprite (#110) Store polygon for interaction in @export var This will allow to keep the data of the polygon in the .tscn file of the room so the InteractionPolygon.polygon data of each prop is overwritten by the room both in runtime and while editing the room. NOTE: The polygon of the InteractionPolygon in each prop is not saved in the prop's scene file yet. Add comments and broke some long lines refs #37: InteractionPolygons icon is now more readable. Fixes for post-import error message (#126) * Fix Output error after importing room * Fix error message after closing room import popup The problem was caused because the popup was being added to the Room tree. Now it is added to Popochiu's main dock. * Temporary fix for bad row naming after import * Imported prop image is placed in the prop's folder Fix Aseprite importer section not showing in Inspector for room refs #37: Added a feature flag to Editor Settings, to disable Aseprite Importer for those who does not use Aseprite. refs #37: Fixed a warning related to EditorInterface being a singleton in 4.2. * refs #37: Removed leftovers after squashing and rebasing over 2.0 * Fix post-rebase issues about main_dock --------- Co-authored-by: carenalga --- .gitignore | 4 +- addons/popochiu/editor/config/config.gd | 19 + addons/popochiu/editor/config/result_codes.gd | 56 ++- .../factories/factory_base_popochiu_obj.gd | 234 ++++++++++++ .../factory_base_popochiu_room_obj.gd | 58 +++ .../factories/factory_popochiu_character.gd | 50 +++ .../factories/factory_popochiu_dialog.gd | 44 +++ .../factories/factory_popochiu_hostspot.gd | 45 +++ .../factory_popochiu_inventory_item.gd | 51 +++ .../editor/factories/factory_popochiu_prop.gd | 57 +++ .../factories/factory_popochiu_region.gd | 50 +++ .../editor/factories/factory_popochiu_room.gd | 58 +++ .../factory_popochiu_walkable_area.gd | 56 +++ .../editor/helpers/popochiu_types_helper.gd | 12 +- .../importers/aseprite/animation_creator.gd | 356 +++++++++++------- .../importers/aseprite/aseprite_controller.gd | 30 ++ .../aseprite/docks/animation_tag_row.gd | 31 +- .../aseprite/docks/animation_tag_row.tscn | 46 ++- ...gd => aseprite_importer_inspector_dock.gd} | 172 ++++++--- ... => aseprite_importer_inspector_dock.tscn} | 46 ++- ...prite_importer_inspector_dock_character.gd | 44 +++ .../aseprite_importer_inspector_dock_room.gd | 121 ++++++ .../aseprite_importer_inspector_plugin.gd | 42 ++- .../editor/main_dock/popochiu_dock.gd | 6 +- addons/popochiu/editor/main_dock/tab_room.gd | 2 +- .../create_character/create_character.gd | 149 +------- .../popups/create_dialog/create_dialog.gd | 82 +--- .../popups/create_hotspot/create_hotspot.gd | 89 +---- .../create_inventory_item.gd | 147 +------- .../editor/popups/create_prop/create_prop.gd | 120 ++---- .../popups/create_region/create_region.gd | 83 +--- .../editor/popups/create_room/create_room.gd | 159 ++------ .../create_walkable_area.gd | 104 +---- .../popochiu/editor/popups/creation_popup.gd | 9 +- .../objects/character/popochiu_character.gd | 21 +- .../objects/clickable/popochiu_clickable.gd | 30 +- .../history/components/dialog_line.tscn | 2 +- .../history/components/interaction_line.tscn | 2 +- .../objects/hotspot/popochiu_hotspot.tscn | 5 +- .../engine/objects/prop/popochiu_prop.tscn | 6 + .../engine/objects/room/popochiu_room.gd | 10 + addons/popochiu/engine/popochiu.gd | 2 +- .../engine/templates/empty_script_template.gd | 8 + addons/popochiu/icons/interaction_polygon.png | Bin 0 -> 156 bytes .../icons/interaction_polygon.png.import | 34 ++ addons/popochiu/popochiu_export_plugin.gd | 10 +- addons/popochiu/popochiu_plugin.gd | 63 +++- addons/popochiu/popochiu_resources.gd | 2 +- popochiu/rooms/house/room_house.tscn | 131 +++++++ .../sources/aseprite/room_exterior.aseprite | Bin 0 -> 9508 bytes 50 files changed, 1856 insertions(+), 1102 deletions(-) create mode 100644 addons/popochiu/editor/factories/factory_base_popochiu_obj.gd create mode 100644 addons/popochiu/editor/factories/factory_base_popochiu_room_obj.gd create mode 100644 addons/popochiu/editor/factories/factory_popochiu_character.gd create mode 100644 addons/popochiu/editor/factories/factory_popochiu_dialog.gd create mode 100644 addons/popochiu/editor/factories/factory_popochiu_hostspot.gd create mode 100644 addons/popochiu/editor/factories/factory_popochiu_inventory_item.gd create mode 100644 addons/popochiu/editor/factories/factory_popochiu_prop.gd create mode 100644 addons/popochiu/editor/factories/factory_popochiu_region.gd create mode 100644 addons/popochiu/editor/factories/factory_popochiu_room.gd create mode 100644 addons/popochiu/editor/factories/factory_popochiu_walkable_area.gd rename addons/popochiu/editor/importers/aseprite/docks/{animation_player_inspector_dock.gd => aseprite_importer_inspector_dock.gd} (72%) rename addons/popochiu/editor/importers/aseprite/docks/{animation_player_inspector_dock.tscn => aseprite_importer_inspector_dock.tscn} (85%) create mode 100644 addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock_character.gd create mode 100644 addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock_room.gd create mode 100644 addons/popochiu/engine/templates/empty_script_template.gd create mode 100644 addons/popochiu/icons/interaction_polygon.png create mode 100644 addons/popochiu/icons/interaction_polygon.png.import create mode 100644 popochiu/rooms/house/room_house.tscn create mode 100644 popochiu/sources/aseprite/room_exterior.aseprite diff --git a/.gitignore b/.gitignore index c9375616..048abf3d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # Addons other than Popochiu # (we use some for development) -# addons/** -# !addons/popochiu/** +addons/* +!addons/popochiu/ # Godot-specific ignores .import/ diff --git a/addons/popochiu/editor/config/config.gd b/addons/popochiu/editor/config/config.gd index 50ec2c43..4d2f9c30 100644 --- a/addons/popochiu/editor/config/config.gd +++ b/addons/popochiu/editor/config/config.gd @@ -2,12 +2,15 @@ extends RefCounted # EDITOR SETTINGS +const _ASEPRITE_IMPORTER_ENABLED_KEY = 'popochiu/import/aseprite/enable_aseprite_importer' const _ASEPRITE_COMMAND_KEY = 'popochiu/import/aseprite/command_path' const _REMOVE_SOURCE_FILES_KEY = 'popochiu/import/aseprite/remove_json_file' # PROJECT SETTINGS const _DEFAULT_IMPORT_ENABLED = 'popochiu/import/aseprite/import_animation_by_default' const _DEFAULT_LOOP_ENABLED = 'popochiu/import/aseprite/loop_animation_by_default' +const _DEFAULT_PROP_VISIBLE_ENABLED = 'popochiu/import/aseprite/new_props_visible_by_default' +const _DEFAULT_PROP_CLICKABLE_ENABLED = 'popochiu/import/aseprite/new_props_clickable_by_default' const _DEFAULT_WIPE_OLD_ANIMS_ENABLED = 'popochiu/import/aseprite/wipe_old_animations' @@ -20,6 +23,7 @@ var _plugin_icons: Dictionary # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░ func initialize_editor_settings(): editor_settings = ei.get_editor_settings() + _initialize_editor_cfg(_ASEPRITE_IMPORTER_ENABLED_KEY, false, TYPE_BOOL) _initialize_editor_cfg(_ASEPRITE_COMMAND_KEY, _default_command(), TYPE_STRING) _initialize_editor_cfg(_REMOVE_SOURCE_FILES_KEY, true, TYPE_BOOL) @@ -27,6 +31,8 @@ func initialize_editor_settings(): func initialize_project_settings(): _initialize_project_cfg(_DEFAULT_IMPORT_ENABLED, true, TYPE_BOOL) _initialize_project_cfg(_DEFAULT_LOOP_ENABLED, true, TYPE_BOOL) + _initialize_project_cfg(_DEFAULT_PROP_VISIBLE_ENABLED, true, TYPE_BOOL) + _initialize_project_cfg(_DEFAULT_PROP_CLICKABLE_ENABLED, true, TYPE_BOOL) _initialize_project_cfg(_DEFAULT_WIPE_OLD_ANIMS_ENABLED, true, TYPE_BOOL) _set_icons() @@ -34,6 +40,10 @@ func initialize_project_settings(): # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ SET & GET ░░░░ +func aseprite_importer_enabled() -> bool: + return _get_editor_setting(_ASEPRITE_IMPORTER_ENABLED_KEY, false) + + func get_command() -> String: return _get_editor_setting(_ASEPRITE_COMMAND_KEY, _default_command()) @@ -54,6 +64,14 @@ func is_default_animation_loop_enabled() -> bool: return _get_project_setting(_DEFAULT_LOOP_ENABLED, true) +func is_default_animation_prop_visible() -> bool: + return _get_project_setting(_DEFAULT_PROP_VISIBLE_ENABLED, true) + + +func is_default_animation_prop_clickable() -> bool: + return _get_project_setting(_DEFAULT_PROP_CLICKABLE_ENABLED, true) + + func is_default_wipe_old_anims_enabled() -> bool: return _get_project_setting(_DEFAULT_WIPE_OLD_ANIMS_ENABLED, true) @@ -81,6 +99,7 @@ func _initialize_editor_cfg(key: String, default_value, type: int, hint: int = P "hint": hint, }) + func _initialize_project_cfg(key: String, default_value, type: int, hint: int = PROPERTY_HINT_NONE): if not ProjectSettings.has_setting(key): ProjectSettings.set_setting(key, default_value) diff --git a/addons/popochiu/editor/config/result_codes.gd b/addons/popochiu/editor/config/result_codes.gd index 59d07c89..a30ee3d4 100644 --- a/addons/popochiu/editor/config/result_codes.gd +++ b/addons/popochiu/editor/config/result_codes.gd @@ -1,21 +1,32 @@ @tool extends RefCounted +class_name ResultCodes - -const SUCCESS = 0 -const ERR_ASEPRITE_CMD_NOT_FULL_PATH = 1 -const ERR_ASEPRITE_CMD_NOT_FOUND = 2 -const ERR_SOURCE_FILE_NOT_FOUND = 3 -const ERR_OUTPUT_FOLDER_NOT_FOUND = 4 -const ERR_ASEPRITE_EXPORT_FAILED = 5 -const ERR_UNKNOWN_EXPORT_MODE = 6 -const ERR_NO_VALID_LAYERS_FOUND = 7 -const ERR_INVALID_ASEPRITE_SPRITESHEET = 8 -const ERR_NO_ANIMATION_PLAYER_FOUND = 9 -const ERR_NO_SPRITE_FOUND = 10 -const ERR_UNNAMED_TAG_DETECTED = 11 -const ERR_TAGS_OPTIONS_ARRAY_EMPTY = 12 - +enum { + ## Base codes + FAILURE, # generic failure state + SUCCESS, # generic success state + ## Aseprite importer errors + ERR_ASEPRITE_CMD_NOT_FULL_PATH, + ERR_ASEPRITE_CMD_NOT_FOUND, + ERR_SOURCE_FILE_NOT_FOUND, + ERR_OUTPUT_FOLDER_NOT_FOUND, + ERR_ASEPRITE_EXPORT_FAILED, + ERR_UNKNOWN_EXPORT_MODE, + ERR_NO_VALID_LAYERS_FOUND, + ERR_INVALID_ASEPRITE_SPRITESHEET, + ERR_NO_ANIMATION_PLAYER_FOUND, + ERR_NO_SPRITE_FOUND, + ERR_UNNAMED_TAG_DETECTED, + ERR_TAGS_OPTIONS_ARRAY_EMPTY, + ## Popochiu Object factories errors + ERR_CANT_CREATE_OBJ_FOLDER, + ERR_CANT_CREATE_OBJ_STATE, + ERR_CANT_OPEN_OBJ_SCRIPT_TEMPLATE, + ERR_CANT_CREATE_OBJ_SCRIPT, + ERR_CANT_SAVE_OBJ_SCENE, + ERR_CANT_SAVE_OBJ_RESOURCE, +} # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░ @@ -23,6 +34,7 @@ static func get_error_message(code: int): ## TODO: these messages are a bit dull, having params would be better. ## Maybe add a param argument match code: + # Aseprite importers error messages ERR_ASEPRITE_CMD_NOT_FULL_PATH: return "Aseprite command not found at given path. Please check \"Editor Settings > Popochiu > Import > Command Path\" to hold the FULL path to a valid Aseprite executable." ERR_ASEPRITE_CMD_NOT_FOUND: @@ -45,5 +57,19 @@ static func get_error_message(code: int): return "Unnamed tag detected" ERR_TAGS_OPTIONS_ARRAY_EMPTY: return "Tags options array is empty" + # Popochiu object factories error messages + ERR_CANT_CREATE_OBJ_FOLDER: + return "Can't create folder to host new Popochiu object" + ERR_CANT_CREATE_OBJ_STATE: + return "Can't create new Popochiu object's state resource (_state.tres, _state.gd)" + ERR_CANT_OPEN_OBJ_SCRIPT_TEMPLATE: + return "Can't open script template for new Popochiu object" + ERR_CANT_CREATE_OBJ_SCRIPT: + return "Can't create new Popochiu object's script file (.gd)" + ERR_CANT_SAVE_OBJ_SCENE: + return "Can't create new Popochiu object's scene (.tscn)" + ERR_CANT_SAVE_OBJ_RESOURCE: + return "Can't create new Popochiu object's resource (.tres)" + # Generic error message _: return "Import failed with code %d" % code diff --git a/addons/popochiu/editor/factories/factory_base_popochiu_obj.gd b/addons/popochiu/editor/factories/factory_base_popochiu_obj.gd new file mode 100644 index 00000000..d296d0aa --- /dev/null +++ b/addons/popochiu/editor/factories/factory_base_popochiu_obj.gd @@ -0,0 +1,234 @@ +extends RefCounted + +const BASE_STATE_TEMPLATE := 'res://addons/popochiu/engine/templates/%s_state_template.gd' +const BASE_SCRIPT_TEMPLATE := 'res://addons/popochiu/engine/templates/%s_template.gd' +const BASE_SCENE_PATH := 'res://addons/popochiu/engine/objects/%s/popochiu_%s.tscn' +const EMPTY_SCRIPT := 'res://addons/popochiu/engine/templates/empty_script_template.gd' + +const Constants := preload('res://addons/popochiu/popochiu_resources.gd') +const MainDock := preload('res://addons/popochiu/editor/main_dock/popochiu_dock.gd') +const PopochiuObjectRow := preload('res://addons/popochiu/editor/main_dock/object_row/popochiu_object_row.gd') + +var _main_dock: Panel = null + +# The following variables are setup on creation +# Names variants and name parameter passed to +# the create method. +var _path_template := '' # always set by child class +var _snake_name := '' +var _pascal_name := '' +var _path_base := '' +var _path_scene = '' +var _path_resource = '' +var _path_state = '' +var _path_script := '' +# The following variables are setup by the sub-class constructor +# to define the type of object to be processed +# TODO: reduce this to just "type", too much redundancy +var _type := -1 +var _type_label := '' +var _type_target := '' +# The following variable is needed because the room factory +# must set a property on the dock row if the room is the +# primary one. +# TODO: remove the need for this using signals #67 +var _dock_row: PopochiuObjectRow +# The following variables are references to the elements +# generated for the creation of the new Popochiu object, +# such as resources, scenes, scripts, state scripts, etc +var _scene: Node +var _resource: Resource +var _state_resource: Resource +var _script: Resource + + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ VIRTUAL ░░░░ +func _init(main_dock: Panel) -> void: + _main_dock = main_dock + + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ SET & GET ░░░░ +func get_obj_scene() -> Node: + return _scene + + +func get_obj_resource() -> Resource: + return _resource + + +func get_state_resource() -> Resource: + return _state_resource + + +func get_obj_script() -> Resource: + return _script + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PRIVATE ░░░ +func _setup_name(obj_name: String) -> void: + _pascal_name = obj_name.to_pascal_case() + _snake_name = obj_name.to_snake_case() + _path_base = _path_template % [_snake_name, _snake_name] + _path_script = _path_base + '.gd' + _path_state = _path_base + '_state.gd' + _path_resource = _path_base + '.tres' + _path_scene = _path_base + '.tscn' + + +func _create_obj_folder() -> int: + # TODO: Check if another object was created in the same PATH. + # TODO: Remove created files if the creation process failed. + if DirAccess.make_dir_recursive_absolute(_path_base.get_base_dir()) != OK: + push_error( + '[Popochiu] Could not create %s directory: %s' % + [_path_base.get_base_dir(), _pascal_name] + ) + return ResultCodes.ERR_CANT_CREATE_OBJ_FOLDER + return ResultCodes.SUCCESS + + +func _create_state_resource() -> int: + var state_template: Script = load( + BASE_STATE_TEMPLATE % _type_label + ).duplicate() + + if ResourceSaver.save(state_template, _path_state) != OK: + push_error( + '[Popochiu] Could not create %s state script: %s' % + [_type_label, _pascal_name] + ) + return ResultCodes.FAILURE + + _state_resource = load(_path_state).new() + _state_resource.script_name = _pascal_name + _state_resource.scene = _path_scene + _state_resource.resource_name = _pascal_name + + if ResourceSaver.save(_state_resource, _path_resource) != OK: + push_error( + "[Popochiu] Couldn't create state resource for %s: %s" % + [_type_label, _pascal_name] + ) + return ResultCodes.ERR_CANT_CREATE_OBJ_STATE + + return ResultCodes.SUCCESS + + +func _copy_script_template() -> int: + var _script: Script = load( + BASE_SCRIPT_TEMPLATE % _type_label + ).duplicate() + + if ResourceSaver.save( _script, _path_script) != OK: + push_error( + "[Popochiu] Couldn't create %s script: %s" % + [_type_label, _path_script] + ) + return ResultCodes.ERR_CANT_CREATE_OBJ_SCRIPT + + return ResultCodes.SUCCESS + + +## Create the script for the object based on the template of its type. +func _create_script_from_template() -> int: + var script_template_file = FileAccess.open( + BASE_SCRIPT_TEMPLATE % _type_label, FileAccess.READ + ) + + if script_template_file == null: + push_error( + "[Popochiu] Couldn't read script template from %s" % + [BASE_SCRIPT_TEMPLATE % _type_label] + ) + return ResultCodes.ERR_CANT_OPEN_OBJ_SCRIPT_TEMPLATE + + var new_code: String = script_template_file.get_as_text() + script_template_file.close() + + new_code = new_code.replace( + '%s_state_template' % _type_label, + '%s_%s_state' % [_type_label, _snake_name] + ) + + new_code = new_code.replace( + 'Data = null', + "Data = load('%s.tres')" % _path_base + ) + + _script = load(EMPTY_SCRIPT).duplicate() + _script.source_code = new_code + + if ResourceSaver.save( _script, _path_script) != OK: + push_error( + "[Popochiu] Couldn't create %s script: %s" % + [_type_label, _path_script] + ) + return ResultCodes.ERR_CANT_CREATE_OBJ_SCRIPT + + return ResultCodes.SUCCESS + + +func _save_obj_scene(obj: Node) -> int: + var packed_scene: PackedScene = PackedScene.new() + packed_scene.pack(obj) + if ResourceSaver.save(packed_scene, _path_scene) != OK: + push_error( + "[Popochiu] Couldn't create %s: %s" % + [_type_label, _path_script] + ) + return ResultCodes.ERR_CANT_SAVE_OBJ_SCENE + + # Load the scene to be get by the calling code + # Instancing the created .tscn file fixes #58 + _scene = load(_path_scene).instantiate() + + return ResultCodes.SUCCESS + + +func _save_obj_resource(obj: Resource) -> int: + if ResourceSaver.save(obj, _path_resource) != OK: + push_error( + "[Popochiu] Couldn't create %s: %s" % + [_type_label, _pascal_name] + ) + return ResultCodes.ERR_CANT_SAVE_OBJ_RESOURCE + + # Load the resource to be get by the calling code + _resource = load(_path_resource) + + return ResultCodes.SUCCESS + + +## Makes a copy of the base scene for the object (e.g. popochiu_room.tscn, +## popochiu_inventory_item.tscn, popochiu_prop.tscn). +func _load_obj_base_scene() -> Node: + var obj = load( + BASE_SCENE_PATH % [_type_label, _type_label] + ).instantiate() + + # The script is assigned first so that other properties will not be + # overwritten by that assignment. + if _script != null: + obj.set_script(load(_path_script)) + + return obj + + +func _add_resource_to_popochiu() -> void: + # Add the created obj to Popochiu's correct list + if _main_dock.add_resource_to_popochiu( + _type_target, + ResourceLoader.load(_path_resource) + ) != OK: + push_error( + "[Popochiu] Couldn't add the created %s to Popochiu: %s" % + [_type_label, _pascal_name] + ) + return + + # Add the object to the proper singleton + PopochiuResources.update_autoloads(true) + + # Update the related list in the dock + _dock_row = (_main_dock as MainDock).add_to_list( + _type, _pascal_name + ) diff --git a/addons/popochiu/editor/factories/factory_base_popochiu_room_obj.gd b/addons/popochiu/editor/factories/factory_base_popochiu_room_obj.gd new file mode 100644 index 00000000..dd0e98d4 --- /dev/null +++ b/addons/popochiu/editor/factories/factory_base_popochiu_room_obj.gd @@ -0,0 +1,58 @@ +extends 'res://addons/popochiu/editor/factories/factory_base_popochiu_obj.gd' + +const CHILD_VISIBLE_IN_ROOM_META = '_popochiu_obj_factory_child_visible_in_room_' +const TabRoom := preload("res://addons/popochiu/editor/main_dock/tab_room.gd") + +var _room_tab: VBoxContainer = null + +# The following variable is setup by the sub-class constructor to +# define the holder node for the new room object (Props, Hotspots, etc) +var _obj_room_group := '' +# The following variables are setup by the _setup_room method +var _room: Node2D = null +var _room_path := '' +var _room_dir := '' + + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░ +func _init(main_dock: Panel) -> void: + super(main_dock) + _room_tab = _main_dock.get_opened_room_tab() + + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PRIVATE ░░░░ +func _setup_room(room: PopochiuRoom) -> void: + _room = room + _room_path = _room.scene_file_path + _room_dir = _room_path.get_base_dir() + # Adding room path to room object path template + _path_template = _room_dir + _path_template + + +# This function adds a child to the new object scene +# marking it as "visible in room scene" +func _add_visible_child(child: Node) -> void: + child.set_meta(CHILD_VISIBLE_IN_ROOM_META, true) + _scene.add_child(child) + + +func _add_resource_to_room() -> void: + # Add the newly created obj to its room + _room.get_node(_obj_room_group).add_child(_scene) + + # Set the ownership for the node plus all it's children + # (this address colliders, polygons, etc) + _scene.owner = _room + for child in _scene.get_children(): + if child.has_meta(CHILD_VISIBLE_IN_ROOM_META): + child.owner = _room + child.remove_meta(CHILD_VISIBLE_IN_ROOM_META) + + # Center the object on the scene + _scene.position = Vector2( + ProjectSettings.get_setting(PopochiuResources.DISPLAY_WIDTH), + ProjectSettings.get_setting(PopochiuResources.DISPLAY_HEIGHT) + ) / 2.0 + + # Save the room scene (it's open in the editor) + EditorInterface.save_scene() diff --git a/addons/popochiu/editor/factories/factory_popochiu_character.gd b/addons/popochiu/editor/factories/factory_popochiu_character.gd new file mode 100644 index 00000000..97a4a947 --- /dev/null +++ b/addons/popochiu/editor/factories/factory_popochiu_character.gd @@ -0,0 +1,50 @@ +extends 'res://addons/popochiu/editor/factories/factory_base_popochiu_obj.gd' +class_name PopochiuCharacterFactory + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░ +func _init(_main_dock: Panel) -> void: + super(_main_dock) + _type = Constants.Types.CHARACTER + _type_label = 'character' + _type_target = 'characters' + _path_template = _main_dock.CHARACTERS_PATH + '%s/character_%s' + + +func create(obj_name: String) -> int: + # If everything goes well, this won't change. + var result_code := ResultCodes.SUCCESS + + # Setup the class variables that depends on the object name + _setup_name(obj_name) + + # Create the folder + result_code = _create_obj_folder() + if result_code != ResultCodes.SUCCESS: return result_code + + # Create the state Resource and a script + # so devs can add extra properties to that state + result_code = _create_state_resource() + if result_code != ResultCodes.SUCCESS: return result_code + + # Create the script populating the template with the right references + result_code = _create_script_from_template() + if result_code != ResultCodes.SUCCESS: return result_code + + # ▓▓▓ LOCAL CODE ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + # Create the instance + var new_obj: PopochiuCharacter = _load_obj_base_scene() + + new_obj.name = 'Character' + _pascal_name + new_obj.script_name = _pascal_name + new_obj.description = _pascal_name.capitalize() + new_obj.cursor = Constants.CURSOR_TYPE.TALK + # ▓▓▓ END OF LOCAL CODE ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + + # Save the scene (.tscn) + result_code = _save_obj_scene(new_obj) + if result_code != ResultCodes.SUCCESS: return result_code + + # Add the object to Popochiu dock list, plus open it in the editor + _add_resource_to_popochiu() + + return result_code diff --git a/addons/popochiu/editor/factories/factory_popochiu_dialog.gd b/addons/popochiu/editor/factories/factory_popochiu_dialog.gd new file mode 100644 index 00000000..e606fe63 --- /dev/null +++ b/addons/popochiu/editor/factories/factory_popochiu_dialog.gd @@ -0,0 +1,44 @@ +extends 'res://addons/popochiu/editor/factories/factory_base_popochiu_obj.gd' +class_name PopochiuDialogFactory + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░ +func _init(_main_dock: Panel) -> void: + super(_main_dock) + _type = Constants.Types.DIALOG + _type_label = 'dialog' + _type_target = 'dialogs' + _path_template = _main_dock.DIALOGS_PATH + '%s/dialog_%s' + + +func create(obj_name: String) -> int: + # If everything goes well, this won't change. + var result_code := ResultCodes.SUCCESS + + # Setup the class variables that depends on the object name + _setup_name(obj_name) + + # Create the folder + result_code = _create_obj_folder() + if result_code != ResultCodes.SUCCESS: return result_code + + # Create the script + result_code = _copy_script_template() + if result_code != ResultCodes.SUCCESS: return result_code + + # ▓▓▓ LOCAL CODE ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + # Create the resource (dialogs are not scenes) + var new_obj := PopochiuDialog.new() + new_obj.set_script(load(_path_script)) + + new_obj.script_name = _pascal_name + new_obj.resource_name = _pascal_name + # ▓▓▓ END OF LOCAL CODE ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + + # Save resource (dialogs are not scenes) + result_code = _save_obj_resource(new_obj) + if result_code != ResultCodes.SUCCESS: return result_code + + # Add the object to Popochiu dock list, plus open it in the editor + _add_resource_to_popochiu() + + return result_code diff --git a/addons/popochiu/editor/factories/factory_popochiu_hostspot.gd b/addons/popochiu/editor/factories/factory_popochiu_hostspot.gd new file mode 100644 index 00000000..4f4294c7 --- /dev/null +++ b/addons/popochiu/editor/factories/factory_popochiu_hostspot.gd @@ -0,0 +1,45 @@ +extends 'res://addons/popochiu/editor/factories/factory_base_popochiu_room_obj.gd' +class_name PopochiuHotspotFactory + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░ +func _init(_main_dock: Panel) -> void: + super(_main_dock) + _type = Constants.Types.HOTSPOT + _type_label = 'hotspot' + _obj_room_group = 'Hotspots' + _path_template = '/hotspots/%s/hotspot_%s' + + +func create(obj_name: String, room: PopochiuRoom) -> int: + # If everything goes well, this won't change. + var result_code := ResultCodes.SUCCESS + + _setup_room(room) + _setup_name(obj_name) + + # Create the folder + result_code = _create_obj_folder() + if result_code != ResultCodes.SUCCESS: return result_code + + # Create the script + result_code = _copy_script_template() + if result_code != ResultCodes.SUCCESS: return result_code + + # ▓▓▓ LOCAL CODE ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + # Create the instance + var new_obj: PopochiuHotspot = _load_obj_base_scene() + + new_obj.name = _pascal_name + new_obj.script_name = _pascal_name + new_obj.description = _snake_name.capitalize() + new_obj.cursor = Constants.CURSOR_TYPE.ACTIVE + + # Save the hostspot scene (.tscn) and put it into _scene class property + result_code = _save_obj_scene(new_obj) + if result_code != ResultCodes.SUCCESS: return result_code + # ▓▓▓ END OF LOCAL CODE ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + + # Add the object to its room + _add_resource_to_room() + + return result_code diff --git a/addons/popochiu/editor/factories/factory_popochiu_inventory_item.gd b/addons/popochiu/editor/factories/factory_popochiu_inventory_item.gd new file mode 100644 index 00000000..97d4fe6e --- /dev/null +++ b/addons/popochiu/editor/factories/factory_popochiu_inventory_item.gd @@ -0,0 +1,51 @@ +extends 'res://addons/popochiu/editor/factories/factory_base_popochiu_obj.gd' +class_name PopochiuInventoryItemFactory + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░ +func _init(_main_dock: Panel) -> void: + super(_main_dock) + _type = Constants.Types.INVENTORY_ITEM + _type_label = 'inventory_item' + _type_target = 'inventory_items' + _path_template = _main_dock.INVENTORY_ITEMS_PATH + '%s/inventory_item_%s' + + +func create(obj_name: String) -> int: + # If everything goes well, this won't change. + var result_code := ResultCodes.SUCCESS + + # Setup the class variables that depends on the object name + _setup_name(obj_name) + + # Create the folder + result_code = _create_obj_folder() + if result_code != ResultCodes.SUCCESS: return result_code + + # Create the state Resource and a script + # so devs can add extra properties to that state + result_code = _create_state_resource() + if result_code != ResultCodes.SUCCESS: return result_code + + # Create the script populating the template with the right references + result_code = _create_script_from_template() + if result_code != ResultCodes.SUCCESS: return result_code + + # ▓▓▓ LOCAL CODE ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + # Create the instance + var new_obj: PopochiuInventoryItem = _load_obj_base_scene() + + new_obj.name = 'Item' + _pascal_name + new_obj.script_name = _pascal_name + new_obj.description = _pascal_name.capitalize() + new_obj.cursor = Constants.CURSOR_TYPE.USE + new_obj.size_flags_vertical = new_obj.SIZE_SHRINK_CENTER + # ▓▓▓ END OF LOCAL CODE ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + + # Save the scene (.tscn) + result_code = _save_obj_scene(new_obj) + if result_code != ResultCodes.SUCCESS: return result_code + + # Add the object to Popochiu dock list, plus open it in the editor + _add_resource_to_popochiu() + + return result_code diff --git a/addons/popochiu/editor/factories/factory_popochiu_prop.gd b/addons/popochiu/editor/factories/factory_popochiu_prop.gd new file mode 100644 index 00000000..1d93d310 --- /dev/null +++ b/addons/popochiu/editor/factories/factory_popochiu_prop.gd @@ -0,0 +1,57 @@ +extends 'res://addons/popochiu/editor/factories/factory_base_popochiu_room_obj.gd' +class_name PopochiuPropFactory + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░ +func _init(_main_dock: Panel) -> void: + super(_main_dock) + + _type = Constants.Types.PROP + _type_label = 'prop' + _obj_room_group = 'Props' + _path_template = '/props/%s/prop_%s' + + +func create( + obj_name: String, room: PopochiuRoom, is_interactive:bool = false, is_visible:bool = true +) -> int: + # If everything goes well, this won't change. + var result_code := ResultCodes.SUCCESS + + _setup_room(room) + _setup_name(obj_name) + + # Create the folder + result_code = _create_obj_folder() + if result_code != ResultCodes.SUCCESS: return result_code + + # Create the script (if the prop is interactive) + result_code = _copy_script_template() + if result_code != ResultCodes.SUCCESS: return result_code + + # ▓▓▓ LOCAL CODE ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + # Create the instance + var new_obj: PopochiuProp = _load_obj_base_scene() + + new_obj.set_script(ResourceLoader.load(_path_script)) + + new_obj.name = _pascal_name + new_obj.script_name = _pascal_name + new_obj.description = _snake_name.capitalize() + new_obj.cursor = Constants.CURSOR_TYPE.ACTIVE + new_obj.clickable = is_interactive + new_obj.visible = is_visible + + if _snake_name in ['bg', 'background']: + new_obj.baseline =\ + -ProjectSettings.get_setting(PopochiuResources.DISPLAY_HEIGHT) / 2.0 + new_obj.z_index = -1 + + # Save the scene (.tscn) and put it into _scene class property + result_code = _save_obj_scene(new_obj) + if result_code != ResultCodes.SUCCESS: return result_code + # ▓▓▓ END OF LOCAL CODE ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + + # Add the object to its room + _add_resource_to_room() + + return result_code diff --git a/addons/popochiu/editor/factories/factory_popochiu_region.gd b/addons/popochiu/editor/factories/factory_popochiu_region.gd new file mode 100644 index 00000000..b9038606 --- /dev/null +++ b/addons/popochiu/editor/factories/factory_popochiu_region.gd @@ -0,0 +1,50 @@ +extends 'res://addons/popochiu/editor/factories/factory_base_popochiu_room_obj.gd' +class_name PopochiuRegionFactory + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░ +func _init(_main_dock: Panel) -> void: + super(_main_dock) + _type = Constants.Types.REGION + _type_label = 'region' + _obj_room_group = 'Regions' + _path_template = '/regions/%s/region_%s' + + +func create(obj_name: String, room: PopochiuRoom) -> int: + # If everything goes well, this won't change. + var result_code := ResultCodes.SUCCESS + + _setup_room(room) + _setup_name(obj_name) + + # Create the folder + result_code = _create_obj_folder() + if result_code != ResultCodes.SUCCESS: return result_code + + # Create the script + result_code = _copy_script_template() + if result_code != ResultCodes.SUCCESS: return result_code + + # ▓▓▓ LOCAL CODE ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + # Create the instance + var new_obj: PopochiuRegion = _load_obj_base_scene() + + new_obj.name = _pascal_name + new_obj.script_name = _pascal_name + new_obj.description = _snake_name.capitalize() + + # Save the scene (.tscn) and put it into _scene class property + result_code = _save_obj_scene(new_obj) + if result_code != ResultCodes.SUCCESS: return result_code + + # Create a collision polygon as a child in the room scene + var collision := CollisionPolygon2D.new() + collision.name = 'InteractionPolygon' + collision.modulate = Color.CYAN + _add_visible_child(collision) + # ▓▓▓ END OF LOCAL CODE ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + + # Add the object to its room + _add_resource_to_room() + + return result_code diff --git a/addons/popochiu/editor/factories/factory_popochiu_room.gd b/addons/popochiu/editor/factories/factory_popochiu_room.gd new file mode 100644 index 00000000..1c826417 --- /dev/null +++ b/addons/popochiu/editor/factories/factory_popochiu_room.gd @@ -0,0 +1,58 @@ +extends 'res://addons/popochiu/editor/factories/factory_base_popochiu_obj.gd' +class_name PopochiuRoomFactory + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░ +func _init(_main_dock: Panel) -> void: + super(_main_dock) + _type = Constants.Types.ROOM + _type_label = 'room' + _type_target = 'rooms' + _path_template = _main_dock.ROOMS_PATH + '%s/room_%s' + + +func create(obj_name: String, set_as_main:bool = false) -> int: + # If everything goes well, this won't change. + var result_code := ResultCodes.SUCCESS + + # Setup the class variables that depends on the object name + _setup_name(obj_name) + + # Create the folder + result_code = _create_obj_folder() + if result_code != ResultCodes.SUCCESS: return result_code + + # Create the state Resource and a script + # so devs can add extra properties to that state + result_code = _create_state_resource() + if result_code != ResultCodes.SUCCESS: return result_code + + # Create the script populating the template with the right references + result_code = _create_script_from_template() + if result_code != ResultCodes.SUCCESS: return result_code + + # ▓▓▓ LOCAL CODE ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + # Create the instance + var new_obj: PopochiuRoom = _load_obj_base_scene() + + new_obj.name = 'Room' + _pascal_name + new_obj.script_name = _pascal_name + # ▓▓▓ END OF LOCAL CODE ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + + # Save the scene (.tscn) + result_code = _save_obj_scene(new_obj) + if result_code != ResultCodes.SUCCESS: return result_code + + # Add the object to Popochiu dock list, plus open it in the editor + _add_resource_to_popochiu() + + # ▓▓▓ LOCAL CODE ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + # Set as main room + # Changed _set_as_main_check.pressed to _set_as_main_check.button_pressed + # in order to fix #56 + if set_as_main: + _main_dock.set_main_scene( _scene.scene_file_path) + # TODO: next line should be in set_main_scene() function! + _dock_row.is_main = true # So the Heart icon shows + # ▓▓▓ LOCAL CODE ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + + return result_code diff --git a/addons/popochiu/editor/factories/factory_popochiu_walkable_area.gd b/addons/popochiu/editor/factories/factory_popochiu_walkable_area.gd new file mode 100644 index 00000000..07b240bf --- /dev/null +++ b/addons/popochiu/editor/factories/factory_popochiu_walkable_area.gd @@ -0,0 +1,56 @@ +extends 'res://addons/popochiu/editor/factories/factory_base_popochiu_room_obj.gd' +class_name PopochiuWalkableAreaFactory + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░ +func _init(_main_dock: Panel) -> void: + super(_main_dock) + _type = Constants.Types.WALKABLE_AREA + _type_label = 'walkable_area' + _obj_room_group = 'WalkableAreas' + _path_template = '/walkable_areas/%s/walkable_area_%s' + + +func create(obj_name: String, room: PopochiuRoom) -> int: + # If everything goes well, this won't change. + var result_code := ResultCodes.SUCCESS + + _setup_room(room) + _setup_name(obj_name) + + # Create the folder + result_code = _create_obj_folder() + if result_code != ResultCodes.SUCCESS: return result_code + + # Create the script + result_code = _copy_script_template() + if result_code != ResultCodes.SUCCESS: return result_code + + # ▓▓▓ LOCAL CODE ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + # Create the instance + var new_obj: PopochiuWalkableArea = _load_obj_base_scene() + + new_obj.name = _pascal_name + new_obj.script_name = _pascal_name + new_obj.description = _snake_name.capitalize() + + # Save the scene (.tscn) and put it into _scene class property + result_code = _save_obj_scene(new_obj) + if result_code != ResultCodes.SUCCESS: return result_code + + # Create a NavigationRegion2D with its polygon as a child in the room scene + var perimeter := NavigationRegion2D.new() + perimeter.name = 'Perimeter' + var polygon := NavigationPolygon.new() + polygon.add_outline(PackedVector2Array([ + Vector2(-10, -10), Vector2(10, -10), Vector2(10, 10), Vector2(-10, 10) + ])) + polygon.make_polygons_from_outlines() + perimeter.navpoly = polygon + perimeter.modulate = Color.GREEN + _add_visible_child(perimeter) + # ▓▓▓ END OF LOCAL CODE ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ + + # Add the object to its room + _add_resource_to_room() + + return result_code diff --git a/addons/popochiu/editor/helpers/popochiu_types_helper.gd b/addons/popochiu/editor/helpers/popochiu_types_helper.gd index e6b28c5e..4be432f9 100644 --- a/addons/popochiu/editor/helpers/popochiu_types_helper.gd +++ b/addons/popochiu/editor/helpers/popochiu_types_helper.gd @@ -1,9 +1,11 @@ extends Resource -# Class used to know the type of a Node (or group of Nodes) when this type of -# validation is required from the plugin. + +# This class holds logic to help with the management of Popochiu objects. # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ +# Functions used to know the type of a Node (or group of Nodes) when this type of +# validation is required from the plugin. static func is_prop(node: Node) -> bool: return node is PopochiuProp @@ -18,3 +20,9 @@ static func is_character(node: Node) -> bool: static func is_walkable_area(node: Node) -> bool: return node is PopochiuWalkableArea + +## TODO: provide more helpers like this maybe? + +## TODO: If and when #67 is ready, add static facade metods to create items +## so we can just pass this object as entry point for all operations +## on Popochiu objects diff --git a/addons/popochiu/editor/importers/aseprite/animation_creator.gd b/addons/popochiu/editor/importers/aseprite/animation_creator.gd index 8d4d4b5f..37808bef 100644 --- a/addons/popochiu/editor/importers/aseprite/animation_creator.gd +++ b/addons/popochiu/editor/importers/aseprite/animation_creator.gd @@ -8,164 +8,208 @@ extends RefCounted const RESULT_CODE = preload("res://addons/popochiu/editor/config/result_codes.gd") const _DEFAULT_AL = "" # Empty string equals default "Global" animation library -var _aseprite = preload("./aseprite_controller.gd").new() - +# Vars configured on initialization var _config: RefCounted var _file_system: EditorFileSystem -var _tags_options_lookup = {} +var _aseprite: RefCounted + +# Vars configured on animations creation +var _target_node: Node +var _player: AnimationPlayer +var _options: Dictionary + +# Class-logic vars +var _spritesheet_metadata = {} +var _target_sprite: Sprite2D +var _output: Dictionary + # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░ -func init(config, editor_file_system: EditorFileSystem = null): +func init(config, aseprite: RefCounted, editor_file_system: EditorFileSystem = null): _config = config _file_system = editor_file_system - _aseprite.init(config) + _aseprite = aseprite +# Public interfaces, dedicated to specific popochiu objects +func create_character_animations(character: Node, player: AnimationPlayer, options: Dictionary): + # Chores + _target_node = character + _player = player + _options = options -func check_aseprite() -> int: - if not _aseprite.check_command_path(): - return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FULL_PATH + # Duly check everything is valid and cleanup animations + var result = _perform_common_checks() + if result != RESULT_CODE.SUCCESS: + return result - if not _aseprite.test_command(): - return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FOUND - - return RESULT_CODE.SUCCESS + # Create the spritesheet + result = await _create_spritesheet_from_file() + if result != RESULT_CODE.SUCCESS: + return result + # Load tags information + result = await _load_spritesheet_metadata() + if result != RESULT_CODE.SUCCESS: + return result -func create_animations(target_node: Node, player: AnimationPlayer, options: Dictionary): - if not _aseprite.check_command_path(): - return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FULL_PATH + # Set the texture in the sprite and configure + # the animations in the AnimationPlayer + _setup_texture() + result = _configure_animations() - if not _aseprite.test_command(): - return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FOUND + return result - if not FileAccess.file_exists(options.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 +func create_prop_animations(prop: Node, aseprite_tag: String, options: Dictionary): + # Chores + _target_node = prop + # TODO: if the prop has no AnimationPlayer, add one! + _player = prop.get_node("AnimationPlayer") + _options = options - var target_sprite = _find_sprite_in_target(target_node) + var prop_animation_name = aseprite_tag.to_snake_case() - if target_sprite == null: - return RESULT_CODE.ERR_NO_SPRITE_FOUND - - if typeof(options.get("tags")) != TYPE_ARRAY: - return RESULT_CODE.ERR_TAGS_OPTIONS_ARRAY_EMPTY - - _load_tags_options_lookup(options.get("tags")) + # Duly check everything is valid and cleanup animations + var result = _perform_common_checks() + if result != RESULT_CODE.SUCCESS: + return result - if (options.wipe_old_animations): - _remove_animations_from_player(player) - - var result = await _create_animations_from_file(target_sprite, player, options) - + # Create the spritesheet + result = await _create_spritesheet_from_tag(aseprite_tag) if result != RESULT_CODE.SUCCESS: - printerr(RESULT_CODE.get_error_message(result)) + return result + # Load tags information + result = await _load_spritesheet_metadata(aseprite_tag) + if result != RESULT_CODE.SUCCESS: + return result -## TODO: Keep this as reference to populate a checkable list of layers -func list_layers(file: String, only_visibles = false): - if not _aseprite.check_command_path(): - return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FULL_PATH - if not _aseprite.test_command(): - return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FOUND - return _aseprite.list_layers(file, only_visibles) + # Set the texture in the sprite and configure + # the animations in the AnimationPlayer + _setup_texture() + result = _configure_animations() + # Sorry, mom... + _player.autoplay = prop.name.to_snake_case() -func list_tags(file: String): - if not _aseprite.check_command_path(): - return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FULL_PATH - if not _aseprite.test_command(): - return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FOUND - return _aseprite.list_tags(file) - + return result # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PRIVATE ░░░░ -func _load_tags_options_lookup(tags: Array = []): - for t in tags: - _tags_options_lookup[t.tag_name] = t - - -func _find_sprite_in_target(target_sprite: Node) -> Node: - if not target_sprite.has_node("Sprite2D"): - return null - return target_sprite.get_node("Sprite2D") - - -func _create_animations_from_file(target_sprite: Node, player: AnimationPlayer, options: Dictionary): - var output +# This function creates a spritesheet with the whole file content +func _create_spritesheet_from_file(): ## TODO: See _aseprite.export_layer() when the time comes to add layers selection - output = _aseprite.export_file(options.source, options.output_folder, options) - - if output.is_empty(): + _output = _aseprite.export_file(_options.source, _options.output_folder, _options) + if _output.is_empty(): return RESULT_CODE.ERR_ASEPRITE_EXPORT_FAILED + return RESULT_CODE.SUCCESS - await _scan_filesystem() - - var result = _import(target_sprite, player, output, options) - if _config.should_remove_source_files(): - DirAccess.remove_absolute(output.data_file) - await _scan_filesystem() - - return result +# This function creates a spritesheet with the frames of a specific tag +# WARNING: it's case sensitive +func _create_spritesheet_from_tag(selected_tag: String): + ## TODO: See _aseprite.export_layer() when the time comes to add layers selection + _output = _aseprite.export_tag(_options.source, selected_tag, _options.output_folder, _options) + if _output.is_empty(): + return RESULT_CODE.ERR_ASEPRITE_EXPORT_FAILED + return RESULT_CODE.SUCCESS -func _remove_animations_from_player(player: AnimationPlayer): - player.remove_animation_library(_DEFAULT_AL) +func _load_spritesheet_metadata(selected_tag: String = ""): + _spritesheet_metadata = { + tags = {}, + frames = {}, + meta = {}, + sprite_sheet = {} + } -func _import(target_sprite: Node, player: AnimationPlayer, data: Dictionary, options: Dictionary): - var source_file = data.data_file - var sprite_sheet = data.sprite_sheet + # Refresh filesystem + await _scan_filesystem() + # Collect all needed info + var source_file = _output.data_file + var sprite_sheet = _output.sprite_sheet + + # Try to access, decode and validate Aseprite JSON output var file = FileAccess.open(source_file, FileAccess.READ) if file == null: return file.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.ERR_INVALID_ASEPRITE_SPRITESHEET + + # Save image metadata from JSON data + _spritesheet_metadata.meta = content.meta - _setup_texture(target_sprite, sprite_sheet, content) - var result = _configure_animations(target_sprite, player, content) - if result != RESULT_CODE.SUCCESS: - return result - - return RESULT_CODE.SUCCESS + # Save frames metadata from JSON data + _spritesheet_metadata.frames = _aseprite.get_content_frames(content) + # Save tags metadata, starting from user's selection, and retrieving + # other information from JSON data + var tags = _options.get("tags").filter(func(tag): return tag.get("import")) -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 + for t in tags: + # If a tag is specified, ignore every other ones + if not selected_tag.is_empty() and selected_tag != t.tag_name: continue + # Create a lookup table for tags + _spritesheet_metadata.tags[t.tag_name] = t + + for ft in _aseprite.get_content_meta_tags(content): + if not _spritesheet_metadata.tags.has(ft.name): continue + _spritesheet_metadata.tags.get(ft.name).merge({ + from = ft.from, + to = ft.to, + direction = ft.direction, + }) + + # If a tag is specified, the tags lookup table should contain + # a single tag information. In this case the to and from properties + # must be shifted back in the [1 - tag_length] range. + if not selected_tag.is_empty(): + # Using a temp variable to make this readable + var t = _spritesheet_metadata.tags[selected_tag] + # NOTE: imagine this goes from 34 to 54, we need to shift + # the range back of a 33 amount, so it goes from 1 to (54 - 33) + t.to = t.to - t.from + 1 + t.from = 0 + _spritesheet_metadata.tags[selected_tag] = t + + # Save spritesheet path from the command output + _spritesheet_metadata.sprite_sheet = sprite_sheet + + # Remove the JSON file if config says so + if _config.should_remove_source_files(): + DirAccess.remove_absolute(_output.data_file) + await _scan_filesystem() + return RESULT_CODE.SUCCESS -func _configure_animations(target_sprite: Node, player: AnimationPlayer, content: Dictionary): - var frames = _aseprite.get_content_frames(content) - if not player.has_animation_library(_DEFAULT_AL): - player.add_animation_library(_DEFAULT_AL, AnimationLibrary.new()) +func _configure_animations(): + if not _player.has_animation_library(_DEFAULT_AL): + _player.add_animation_library(_DEFAULT_AL, AnimationLibrary.new()) - if content.meta.has("frameTags") and content.meta.frameTags.size() > 0: + if _spritesheet_metadata.tags.size() > 0: var result = RESULT_CODE.SUCCESS - for tag in content.meta.frameTags: - if not _tags_options_lookup.get(tag.name).get("import"): - continue - var selected_frames = frames.slice(tag.from, tag.to+1) # slice is [) - result = _add_animation_frames(target_sprite, player, tag.name, selected_frames, tag.direction) + # RESTART_FROM_HERE: WARNING: in case of prop and inventory, the JSON file contains + # the whole set of tags, so we must take the tag.from and tag.to and remap the range + # from "1" to "tag.to +1 - tag.from + 1" (do the math an you'll see that's correct) + for tag in _spritesheet_metadata.tags.values(): + var selected_frames = _spritesheet_metadata.frames.slice(tag.from, tag.to + 1) # slice is [) + result = _add_animation_frames(tag.tag_name, selected_frames, tag.direction) if result != RESULT_CODE.SUCCESS: break return result else: - return _add_animation_frames(target_sprite, player, "default", frames) + return _add_animation_frames("default", _spritesheet_metadata.frames) -func _add_animation_frames(target_sprite: Node, player: AnimationPlayer, anim_name: String, frames: Array, direction = 'forward'): +func _add_animation_frames(anim_name: String, frames: Array, direction = 'forward'): # TODO: ATM there is no way to assign a walk/talk/grab/idle animation # with a different name than the standard ones. The engine is searching for # lowercase names in the AnimationPlayer, thus we are forcing snake_case @@ -173,25 +217,25 @@ func _add_animation_frames(target_sprite: Node, player: AnimationPlayer, anim_na # We have to add methods or properties to the Character to assign different # animations (but maybe we can do with anim_prefix or other strategies). var animation_name = anim_name.to_snake_case() - var is_loopable = _tags_options_lookup.get(anim_name).get("loops") + var is_loopable = _spritesheet_metadata.tags.get(anim_name).get("loops") # Create animation library if it doesn't exist # This is always true if the user selected to wipe old animations. # See _remove_animations_from_player() function. - if not player.has_animation_library(_DEFAULT_AL): - player.add_animation_library(_DEFAULT_AL, AnimationLibrary.new()) + if not _player.has_animation_library(_DEFAULT_AL): + _player.add_animation_library(_DEFAULT_AL, AnimationLibrary.new()) - if not player.get_animation_library(_DEFAULT_AL).has_animation(animation_name): - player.get_animation_library(_DEFAULT_AL).add_animation(animation_name, Animation.new()) + if not _player.get_animation_library(_DEFAULT_AL).has_animation(animation_name): + _player.get_animation_library(_DEFAULT_AL).add_animation(animation_name, Animation.new()) # Here is where animations are created. # TODO: we need to "fork" the logic so that Character has a single spritesheet # containing all tags, while Rooms/Props and Inventory Items has a single spritesheet # for each tag, so that you can have each prop with its own animation (PnC) - var animation = player.get_animation(animation_name) - _create_meta_tracks(target_sprite, player, animation) - var frame_track = _get_property_track_path(player, target_sprite, "frame") - var frame_track_index = _create_track(target_sprite, animation, frame_track) + var animation = _player.get_animation(animation_name) + _create_meta_tracks(animation) + var frame_track = _get_property_track_path("frame") + var frame_track_index = _create_track(_target_sprite, animation, frame_track) if direction == 'reverse': frames.reverse() @@ -199,7 +243,7 @@ func _add_animation_frames(target_sprite: Node, player: AnimationPlayer, anim_na var animation_length = 0 for frame in frames: - var frame_key = _get_frame_key(target_sprite, frame) + var frame_key = _get_frame_key(frame) animation.track_insert_key(frame_track_index, animation_length, frame_key) animation_length += frame.duration / 1000 ## NOTE: animation_length is in seconds @@ -210,7 +254,7 @@ func _add_animation_frames(target_sprite: Node, player: AnimationPlayer, anim_na frames.reverse() for frame in frames: - var frame_key = _get_frame_key(target_sprite, frame) + var frame_key = _get_frame_key(frame) animation.track_insert_key(frame_track_index, animation_length, frame_key) animation_length += frame.duration / 1000 ## NOTE: animation_length is in seconds @@ -220,8 +264,7 @@ func _add_animation_frames(target_sprite: Node, player: AnimationPlayer, anim_na return RESULT_CODE.SUCCESS -## TODO: insert validate tokens in amination name - +## TODO: insert validate tokens in animation name func _create_track(target_sprite: Node, animation: Animation, track: String): var track_index = animation.find_track(track, Animation.TYPE_VALUE) @@ -238,8 +281,8 @@ func _create_track(target_sprite: Node, animation: Animation, track: String): return track_index -func _get_property_track_path(player: AnimationPlayer, target_sprite: Node, prop: String) -> String: - var node_path = player.get_node(player.root_node).get_path_to(target_sprite) +func _get_property_track_path(prop: String) -> String: + var node_path = _player.get_node(_player.root_node).get_path_to(_target_sprite) return "%s:%s" % [node_path, prop] @@ -263,38 +306,39 @@ func _remove_properties_from_path(path: NodePath) -> NodePath: # What follow is logic specifically gathered for Sprite elements. TextureRect should # be treated in a different way (see texture_rect_animation_creator.gd file in # original Aseprite Wizard plugin by Vinicius Gerevini) +func _setup_texture(): + # Load texture in target sprite (ignoring cache and forcing a refres) + var texture = ResourceLoader.load(_spritesheet_metadata.sprite_sheet, 'Image', ResourceLoader.CACHE_MODE_IGNORE) + texture.take_over_path(_spritesheet_metadata.sprite_sheet) + _target_sprite.texture = texture -func _setup_texture(sprite: Node, sprite_sheet: String, content: Dictionary): - var texture = _load_texture(sprite_sheet) - sprite.texture = texture - - if content.frames.is_empty(): + if _spritesheet_metadata.frames.is_empty(): return - sprite.hframes = content.meta.size.w / content.frames[0].sourceSize.w - sprite.vframes = content.meta.size.h / content.frames[0].sourceSize.h + _target_sprite.hframes = _spritesheet_metadata.meta.size.w / _spritesheet_metadata.frames[0].sourceSize.w + _target_sprite.vframes = _spritesheet_metadata.meta.size.h / _spritesheet_metadata.frames[0].sourceSize.h -func _create_meta_tracks(sprite: Node, player: AnimationPlayer, animation: Animation): - var texture_track = _get_property_track_path(player, sprite, "texture") - var texture_track_index = _create_track(sprite, animation, texture_track) - animation.track_insert_key(texture_track_index, 0, sprite.texture) +func _create_meta_tracks(animation: Animation): + var texture_track = _get_property_track_path("texture") + var texture_track_index = _create_track(_target_sprite, animation, texture_track) + animation.track_insert_key(texture_track_index, 0, _target_sprite.texture) - var hframes_track = _get_property_track_path(player, sprite, "hframes") - var hframes_track_index = _create_track(sprite, animation, hframes_track) - animation.track_insert_key(hframes_track_index, 0, sprite.hframes) + var hframes_track = _get_property_track_path("hframes") + var hframes_track_index = _create_track(_target_sprite, animation, hframes_track) + animation.track_insert_key(hframes_track_index, 0, _target_sprite.hframes) - var vframes_track = _get_property_track_path(player, sprite, "vframes") - var vframes_track_index = _create_track(sprite, animation, vframes_track) - animation.track_insert_key(vframes_track_index, 0, sprite.vframes) + var vframes_track = _get_property_track_path("vframes") + var vframes_track_index = _create_track(_target_sprite, animation, vframes_track) + animation.track_insert_key(vframes_track_index, 0, _target_sprite.vframes) - var visible_track = _get_property_track_path(player, sprite, "visible") - var visible_track_index = _create_track(sprite, animation, visible_track) + var visible_track = _get_property_track_path("visible") + var visible_track_index = _create_track(_target_sprite, animation, visible_track) animation.track_insert_key(visible_track_index, 0, true) -func _get_frame_key(sprite: Node, frame: Dictionary): - return _calculate_frame_index(sprite,frame) +func _get_frame_key(frame: Dictionary): + return _calculate_frame_index(_target_sprite,frame) func _calculate_frame_index(sprite: Node, frame: Dictionary) -> int: @@ -302,3 +346,41 @@ func _calculate_frame_index(sprite: Node, frame: Dictionary) -> int: var row = floor(frame.frame.y * sprite.vframes / sprite.texture.get_height()) return (row * sprite.hframes) + column + +func _perform_common_checks(): + # Checks + if not _aseprite.check_command_path(): + return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FULL_PATH + + if not _aseprite.test_command(): + return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FOUND + + if not FileAccess.file_exists(_options.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 + + _target_sprite = _find_sprite_in_target() + + if _target_sprite == null: + return RESULT_CODE.ERR_NO_SPRITE_FOUND + + if typeof(_options.get("tags")) != TYPE_ARRAY: + return RESULT_CODE.ERR_TAGS_OPTIONS_ARRAY_EMPTY + + if (_options.wipe_old_animations): + _remove_animations_from_player(_player) + + return RESULT_CODE.SUCCESS + + +func _find_sprite_in_target() -> Node: + if not _target_node.has_node("Sprite2D"): + return null + return _target_node.get_node("Sprite2D") + + +func _remove_animations_from_player(player: AnimationPlayer): + if player.has_animation_library(_DEFAULT_AL): + player.remove_animation_library(_DEFAULT_AL) diff --git a/addons/popochiu/editor/importers/aseprite/aseprite_controller.gd b/addons/popochiu/editor/importers/aseprite/aseprite_controller.gd index 644fb643..d4ddc78e 100644 --- a/addons/popochiu/editor/importers/aseprite/aseprite_controller.gd +++ b/addons/popochiu/editor/importers/aseprite/aseprite_controller.gd @@ -79,6 +79,32 @@ func export_layer(file_name: String, layer_name: String, output_folder: String, } +# IMPROVE: See if we can extract JSON data limited to the single tag +# (so we don't have to reckon offset framerange) +func export_tag(file_name: String, tag_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, tag_name] + var sprite_sheet = "%s/%s%s.png" % [output_dir, output_prefix, tag_name] + var output = [] + var arguments = _export_command_common_arguments(file_name, data_file, sprite_sheet) + arguments.push_front(tag_name) + arguments.push_front("--tag") + + _add_sheet_type_arguments(arguments, options) + + var exit_code = _execute(arguments, output) + if exit_code != 0: + printerr('[Popochiu] Aseprite: Failed to export tag spritesheet. Command output follows:') + print(output) + return {} + + return { + 'data_file': data_file.replace("./", "res://"), + "sprite_sheet": sprite_sheet.replace("./", "res://") + } + + func list_layers(file_name: String, only_visible = false) -> Array: var output = [] var arguments = ["-b", "--list-layers", file_name] @@ -118,6 +144,10 @@ func get_content_frames(content): return content.frames if typeof(content.frames) == TYPE_ARRAY else content.frames.values() +func get_content_meta_tags(content): + return content.meta.frameTags if content.meta.has("frameTags") else [] + + func check_command_path(): # On Linux, MacOS or other *nix platforms, nothing to do if not OS.get_name() in ["Windows", "UWP"]: diff --git a/addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.gd b/addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.gd index 9d7b0b33..294c1664 100644 --- a/addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.gd +++ b/addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.gd @@ -11,14 +11,19 @@ var _anim_tag_state: Dictionary = {} @onready var tag_name_label = $HBoxContainer/TagName @onready var import_toggle = $Panel/HBoxContainer/Import @onready var loops_toggle = $Panel/HBoxContainer/Loops - +@onready var separator = $Panel/HBoxContainer/Separator +@onready var visible_toggle = $Panel/HBoxContainer/Visible +@onready var clickable_toggle = $Panel/HBoxContainer/Clickable # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ GODOT ░░░░ func _ready(): - loops_toggle.icon = get_theme_icon('Loop', 'EditorIcons') + # Common toggle icons import_toggle.icon = get_theme_icon('Load', 'EditorIcons') - + loops_toggle.icon = get_theme_icon('Loop', 'EditorIcons') + # Room-related toggle icons + visible_toggle.icon = get_theme_icon('GuiVisibilityVisible', 'EditorIcons') + clickable_toggle.icon = get_theme_icon('ToolSelect', 'EditorIcons') # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░ @@ -32,6 +37,10 @@ func init(config, tag_cfg: Dictionary): _anim_tag_state.merge(tag_cfg, true) _setup_scene() +func show_prop_buttons(): + separator.visible = true + visible_toggle.visible = true + clickable_toggle.visible = true # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ SET & GET ░░░░ @@ -39,12 +48,14 @@ func get_cfg() -> Dictionary: return _anim_tag_state - # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PRIVATE ░░░░ func _setup_scene(): import_toggle.button_pressed = _anim_tag_state.import loops_toggle.button_pressed = _anim_tag_state.loops tag_name_label.text = _anim_tag_state.tag_name + visible_toggle.button_pressed = _anim_tag_state.prop_visible + clickable_toggle.button_pressed = _anim_tag_state.prop_clickable + emit_signal("tag_state_changed") func _load_default_tag_state() -> Dictionary: @@ -52,10 +63,11 @@ func _load_default_tag_state() -> Dictionary: "tag_name": "", "import": _config.is_default_animation_import_enabled(), "loops": _config.is_default_animation_loop_enabled(), + "prop_visible": _config.is_default_animation_prop_visible(), + "prop_clickable": _config.is_default_animation_prop_clickable(), } - func _on_import_toggled(button_pressed): _anim_tag_state.import = button_pressed emit_signal("tag_state_changed") @@ -64,3 +76,12 @@ func _on_import_toggled(button_pressed): func _on_loops_toggled(button_pressed): _anim_tag_state.loops = button_pressed emit_signal("tag_state_changed") + + +func _on_visible_toggled(button_pressed): + _anim_tag_state.prop_visible = button_pressed + emit_signal("tag_state_changed") + +func _on_clickable_toggled(button_pressed): + _anim_tag_state.prop_clickable = button_pressed + emit_signal("tag_state_changed") diff --git a/addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.tscn b/addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.tscn index 2e704408..3193d194 100644 --- a/addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.tscn +++ b/addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.tscn @@ -1,10 +1,10 @@ -[gd_scene load_steps=5 format=3 uid="uid://rphyltbm12m4"] +[gd_scene load_steps=6 format=3 uid="uid://rphyltbm12m4"] [ext_resource type="Script" path="res://addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.gd" id="1"] [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_77wem"] -[sub_resource type="Image" id="Image_t6px5"] +[sub_resource type="Image" id="Image_33eob"] data = { "data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 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, 131, 255, 255, 255, 0, 255, 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, 131, 255, 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, 93, 93, 131, 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, 252, 255, 93, 93, 252, 255, 93, 93, 252, 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, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 255, 128, 128, 4, 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, 93, 93, 55, 255, 97, 97, 58, 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, 97, 97, 42, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 98, 98, 47, 255, 97, 97, 42, 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, 93, 93, 233, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 94, 94, 46, 255, 93, 93, 236, 255, 93, 93, 233, 255, 97, 97, 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, 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, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 93, 93, 252, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 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", @@ -13,8 +13,10 @@ data = { "width": 16 } -[sub_resource type="ImageTexture" id="ImageTexture_317mf"] -image = SubResource("Image_t6px5") +[sub_resource type="ImageTexture" id="ImageTexture_i0lab"] +image = SubResource("Image_33eob") + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_sd1l8"] [node name="AnimationTagRow" type="HBoxContainer"] offset_right = 320.0 @@ -34,11 +36,39 @@ size_flags_horizontal = 3 theme_override_styles/panel = SubResource("StyleBoxEmpty_77wem") [node name="HBoxContainer" type="HBoxContainer" parent="Panel"] -layout_mode = 0 +layout_mode = 1 +anchors_preset = 1 anchor_left = 1.0 anchor_right = 1.0 offset_left = -60.0 offset_bottom = 20.0 +grow_horizontal = 0 + +[node name="Visible" type="Button" parent="Panel/HBoxContainer"] +visible = false +layout_mode = 2 +size_flags_horizontal = 4 +size_flags_vertical = 3 +tooltip_text = "This prop will be visible" +toggle_mode = true +icon = SubResource("ImageTexture_i0lab") +flat = true + +[node name="Clickable" type="Button" parent="Panel/HBoxContainer"] +visible = false +layout_mode = 2 +size_flags_horizontal = 4 +size_flags_vertical = 3 +tooltip_text = "This prop will be clickable" +toggle_mode = true +icon = SubResource("ImageTexture_i0lab") +flat = true + +[node name="Separator" type="Panel" parent="Panel/HBoxContainer"] +visible = false +custom_minimum_size = Vector2(1, 0) +layout_mode = 2 +theme_override_styles/panel = SubResource("StyleBoxFlat_sd1l8") [node name="Import" type="Button" parent="Panel/HBoxContainer"] layout_mode = 2 @@ -46,7 +76,7 @@ size_flags_horizontal = 4 size_flags_vertical = 3 tooltip_text = "Import this animation" toggle_mode = true -icon = SubResource("ImageTexture_317mf") +icon = SubResource("ImageTexture_i0lab") flat = true [node name="Loops" type="Button" parent="Panel/HBoxContainer"] @@ -55,8 +85,10 @@ size_flags_horizontal = 4 size_flags_vertical = 3 tooltip_text = "Set animation as looping" toggle_mode = true -icon = SubResource("ImageTexture_317mf") +icon = SubResource("ImageTexture_i0lab") flat = true +[connection signal="toggled" from="Panel/HBoxContainer/Visible" to="." method="_on_visible_toggled"] +[connection signal="toggled" from="Panel/HBoxContainer/Clickable" to="." method="_on_clickable_toggled"] [connection signal="toggled" from="Panel/HBoxContainer/Import" to="." method="_on_import_toggled"] [connection signal="toggled" from="Panel/HBoxContainer/Loops" to="." method="_on_loops_toggled"] diff --git a/addons/popochiu/editor/importers/aseprite/docks/animation_player_inspector_dock.gd b/addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock.gd similarity index 72% rename from addons/popochiu/editor/importers/aseprite/docks/animation_player_inspector_dock.gd rename to addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock.gd index 3bacf4c0..78c380bd 100644 --- a/addons/popochiu/editor/importers/aseprite/docks/animation_player_inspector_dock.gd +++ b/addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock.gd @@ -1,26 +1,33 @@ @tool extends PanelContainer +# TODO: review coding standards for those constants const RESULT_CODE = preload("res://addons/popochiu/editor/config/result_codes.gd") const LOCAL_OBJ_CONFIG = preload("res://addons/popochiu/editor/config/local_obj_config.gd") -# TODO: import a class without breaking coding standards... how? -const AnimationTagRow = preload("res://addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.gd") +## TODO: this can be specialized, even if for a two buttons... ? +const AnimationTagRow =\ +preload("res://addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.gd") var scene: Node var target_node: Node var config: RefCounted +var main_dock: Panel var file_system: EditorFileSystem # External logic -var _animation_creator = preload("res://addons/popochiu/editor/importers/aseprite/animation_creator.gd").new() -var _animation_tag_row_scene: PackedScene = preload('res://addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.tscn') +var _animation_tag_row_scene: PackedScene =\ +preload('res://addons/popochiu/editor/importers/aseprite/docks/animation_tag_row.tscn') +var _aseprite = preload("../aseprite_controller.gd").new() ## TODO: should be absolute? + +# References for children scripts +var _root_node: Node +var _options: Dictionary # Importer parameters variables var _source: String = "" var _tags_cache: Array = [] -var _animation_player_path: String var _file_dialog_aseprite: FileDialog var _output_folder_dialog: FileDialog var _importing := false @@ -31,45 +38,77 @@ var _out_folder_default := "[Same as scene]" # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ GODOT ░░░░ func _ready(): _set_elements_styles() - - if not target_node.has_node("AnimationPlayer"): - printerr(RESULT_CODE.get_error_message(RESULT_CODE.ERR_NO_ANIMATION_PLAYER_FOUND)) - return - - _animation_player_path = target_node.get_node("AnimationPlayer").get_path() - # Instantiate animation creator - _animation_creator.init(config, file_system) + if not config.aseprite_importer_enabled(): + _show_info() + return + # Init Aseprite helper library + _aseprite.init(config) # Check access to Aseprite executable - var result = _animation_creator.check_aseprite() + var result = _check_aseprite() if result == RESULT_CODE.SUCCESS: _show_importer() else: printerr(RESULT_CODE.get_error_message(result)) - _hide_importer() - + _show_warning() # Load inspector dock configuration from node var cfg = LOCAL_OBJ_CONFIG.load_config(target_node) if cfg == null: _load_default_config() + _set_options_visible(true) else: _load_config(cfg) - - _set_options_visible(cfg.get("op_exp")) + _set_options_visible(cfg.get("op_exp")) # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PRIVATE ░░░░ +func _check_aseprite() -> int: + _aseprite.init(config) + + if not _aseprite.check_command_path(): + return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FULL_PATH + + if not _aseprite.test_command(): + return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FOUND + + return RESULT_CODE.SUCCESS + + +func _list_tags(file: String): + if not _aseprite.check_command_path(): + return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FULL_PATH + if not _aseprite.test_command(): + return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FOUND + return _aseprite.list_tags(file) + + +## TODO: Currently unused. keeping this as reference +## to populate a checkable list of layers +func _list_layers(file: String, only_visibles = false): + if not _aseprite.check_command_path(): + return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FULL_PATH + if not _aseprite.test_command(): + return RESULT_CODE.ERR_ASEPRITE_CMD_NOT_FOUND + return _aseprite.list_layers(file, only_visibles) + + func _load_config(cfg): if cfg.has("source"): _set_source(cfg.source) _output_folder = cfg.get("o_folder", "") - get_node('%OutFolderButton').text = _output_folder if _output_folder != "" else _out_folder_default + get_node('%OutFolderButton').text = ( + _output_folder if _output_folder != "" else _out_folder_default + ) get_node('%OutFileName').text = cfg.get("o_name", "") - get_node('%VisibleLayersCheckButton').set_pressed_no_signal(cfg.get("only_visible_layers", false)) - get_node('%WipeOldAnimationsCheckButton').set_pressed_no_signal(cfg.get("wipe_old_anims", false)) + get_node('%VisibleLayersCheckButton').set_pressed_no_signal( + cfg.get("only_visible_layers", false) + ) + get_node('%WipeOldAnimationsCheckButton').set_pressed_no_signal( + cfg.get("wipe_old_anims", false) + ) _set_options_visible(cfg.get("op_exp", false)) _populate_tags(cfg.get("tags", [])) @@ -77,8 +116,8 @@ func _load_config(cfg): func _save_config(): _update_tags_cache() + var cfg := { - "player": _animation_player_path, "source": _source, "tags": _tags_cache, "op_exp": get_node('%Options').visible, @@ -106,7 +145,9 @@ func _load_default_config(): get_node('%OutFolderButton').text = "[empty]" get_node('%OutFileName').clear() get_node('%VisibleLayersCheckButton').set_pressed_no_signal(false) - get_node('%WipeOldAnimationsCheckButton').set_pressed_no_signal(config.is_default_wipe_old_anims_enabled()) + get_node('%WipeOldAnimationsCheckButton').set_pressed_no_signal( + config.is_default_wipe_old_anims_enabled() + ) func _set_source(source): @@ -136,39 +177,27 @@ func _on_rescan_pressed(): func _on_import_pressed(): if _importing: return + _importing = true - - 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 - + _root_node = get_tree().get_edited_scene_root() + if _source == "": _show_message("Aseprite file not selected") _importing = false return - - var options = { + + _options = { "source": ProjectSettings.globalize_path(_source), "tags": _tags_cache, - "output_folder": _output_folder if _output_folder != "" else root.scene_file_path.get_base_dir(), + "output_folder": ( + _output_folder if _output_folder != "" else _root_node.scene_file_path.get_base_dir() + ), "output_filename": get_node('%OutFileName').text, "only_visible_layers": get_node('%VisibleLayersCheckButton').is_pressed(), "wipe_old_animations": get_node('%WipeOldAnimationsCheckButton').is_pressed(), } - - _save_config() - - var result = await _animation_creator.create_animations(target_node, root.get_node(_animation_player_path), options) - _importing = false - if typeof(result) == TYPE_INT and result != RESULT_CODE.SUCCESS: - printerr(RESULT_CODE.get_error_message(result)) - _show_message("Some errors occurred. Please check output panel.", "Warning!") - else: - _show_message("%d animation tags processed." % [_tags_cache.size()], "Done!") + _save_config() func _on_reset_pressed(): @@ -220,16 +249,23 @@ func _populate_tags(tags: Array): get_node('%Tags').add_child(tag_row) tag_row.init(config, t) tag_row.connect("tag_state_changed", Callable(self, "_save_config")) - + _customize_tag_ui(tag_row) + # Invoke customization hook implementable in child classes _update_tags_cache() +func _customize_tag_ui(tagrow: AnimationTagRow): + ## This can be implemented by child classes if necessary + pass + + func _empty_tags_container(): # Clean the inspector tags container empty for tl in get_node('%Tags').get_children(): get_node('%Tags').remove_child(tl) tl.queue_free() + func _update_tags_cache(): _tags_cache = _get_tags_from_ui() @@ -261,7 +297,7 @@ func _get_tags_from_ui() -> Array: func _get_tags_from_source() -> Array: - var tags_found = _animation_creator.list_tags(ProjectSettings.globalize_path(_source)) + var tags_found = _list_tags(ProjectSettings.globalize_path(_source)) if typeof(tags_found) == TYPE_INT: printerr(RESULT_CODE.get_error_message(tags_found)) return [] @@ -275,14 +311,28 @@ func _get_tags_from_source() -> Array: return tags_list -func _show_message(message: String, title: String = ""): +func _show_message( + message: String, title: String = "", object: Object = null, method := "" +): var _warning_dialog = AcceptDialog.new() - get_parent().add_child(_warning_dialog) + if title != "": _warning_dialog.title = title + _warning_dialog.dialog_text = message + _warning_dialog.popup_window = true + + var callback := Callable(_warning_dialog, "queue_free") + + if is_instance_valid(object) and not method.is_empty(): + callback = func(): + object.call(method) + + _warning_dialog.confirmed.connect(callback) + _warning_dialog.close_requested.connect(callback) + + main_dock.add_child(_warning_dialog) _warning_dialog.popup_centered() - _warning_dialog.connect("close_requested", Callable(_warning_dialog, "queue_free")) func _show_confirmation(message: String, title: String = ""): @@ -302,7 +352,9 @@ func _on_options_title_toggled(button_pressed): func _set_options_visible(is_visible): get_node('%Options').visible = is_visible - get_node('%OptionsTitle').icon = config.get_icon("expanded") if is_visible else config.get_icon("collapsed") + get_node('%OptionsTitle').icon = ( + config.get_icon("expanded") if is_visible else config.get_icon("collapsed") + ) func _on_out_folder_pressed(): @@ -324,7 +376,9 @@ func _create_output_folder_selection(): func _on_output_folder_selected(path): _output_folder = path - get_node('%OutFolderButton').text = _output_folder if _output_folder != "" else _out_folder_default + get_node('%OutFolderButton').text = ( + _output_folder if _output_folder != "" else _out_folder_default + ) _output_folder_dialog.queue_free() _save_config() @@ -345,15 +399,21 @@ func _set_elements_styles(): get_node('%WarningLabel').add_theme_color_override('font_color', Color('c46c71')) -func _hide_importer(): +func _show_info(): + get_node('%Info').visible = true + get_node('%Warning').visible = false get_node('%Importer').visible = false - get_node('%Warning').visible = true +func _show_warning(): + get_node('%Info').visible = false + get_node('%Warning').visible = true + get_node('%Importer').visible = false + + func _show_importer(): - get_node('%Importer').visible = true + get_node('%Info').visible = false get_node('%Warning').visible = false + get_node('%Importer').visible = true - -## TODO: IMPORTANT AND FIRST IN LINE! The importer has different behavior for Characters, Rooms and Inventory items! ## TODO: Introduce layer selection list, more or less as tags diff --git a/addons/popochiu/editor/importers/aseprite/docks/animation_player_inspector_dock.tscn b/addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock.tscn similarity index 85% rename from addons/popochiu/editor/importers/aseprite/docks/animation_player_inspector_dock.tscn rename to addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock.tscn index dce65737..6fe2020e 100644 --- a/addons/popochiu/editor/importers/aseprite/docks/animation_player_inspector_dock.tscn +++ b/addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock.tscn @@ -1,30 +1,23 @@ -[gd_scene load_steps=5 format=3 uid="uid://qtefsrha1b7s"] - -[ext_resource type="Script" path="res://addons/popochiu/editor/importers/aseprite/docks/animation_player_inspector_dock.gd" id="1"] +[gd_scene load_steps=4 format=3 uid="uid://bcanby6n3eahm"] [sub_resource type="StyleBoxEmpty" id="1"] -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_lr3up"] +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_wwoxk"] bg_color = Color(0, 0, 0, 1) -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_uvby7"] +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ctsm1"] content_margin_left = 4.0 content_margin_top = 4.0 content_margin_right = 4.0 content_margin_bottom = 4.0 -bg_color = Color(1, 0.365, 0.365, 1) +bg_color = Color(1, 0.364706, 0.364706, 1) draw_center = false -border_width_left = 2 -border_width_top = 2 -border_width_right = 2 -border_width_bottom = 2 corner_detail = 1 [node name="sprite_inspector_dock" type="PanelContainer"] offset_right = 14.0 offset_bottom = 14.0 theme_override_styles/panel = SubResource("1") -script = ExtResource("1") [node name="margin" type="MarginContainer" parent="."] layout_mode = 2 @@ -62,7 +55,7 @@ text = "Rescan" [node name="TagsTitleBar" type="PanelContainer" parent="margin/Importer"] unique_name_in_owner = true layout_mode = 2 -theme_override_styles/panel = SubResource("StyleBoxFlat_lr3up") +theme_override_styles/panel = SubResource("StyleBoxFlat_wwoxk") [node name="TagsTitle" type="Button" parent="margin/Importer/TagsTitleBar"] layout_mode = 2 @@ -77,7 +70,7 @@ layout_mode = 2 [node name="OptionsTitleBar" type="PanelContainer" parent="margin/Importer"] unique_name_in_owner = true layout_mode = 2 -theme_override_styles/panel = SubResource("StyleBoxFlat_lr3up") +theme_override_styles/panel = SubResource("StyleBoxFlat_wwoxk") [node name="OptionsTitle" type="Button" parent="margin/Importer/OptionsTitleBar"] unique_name_in_owner = true @@ -168,6 +161,7 @@ text = "Reset Preferences" [node name="Warning" type="VBoxContainer" parent="margin"] unique_name_in_owner = true +visible = false layout_mode = 2 [node name="HBoxContainer" type="HBoxContainer" parent="margin/Warning"] @@ -179,7 +173,7 @@ custom_minimum_size = Vector2(222, 50) layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 3 -theme_override_styles/panel = SubResource("StyleBoxFlat_uvby7") +theme_override_styles/panel = SubResource("StyleBoxFlat_ctsm1") [node name="WarningLabel" type="Label" parent="margin/Warning/HBoxContainer/WarningPanel"] unique_name_in_owner = true @@ -194,6 +188,30 @@ text = "Error loading Aseprite Importer! Check Output panel for details." horizontal_alignment = 1 +[node name="Info" type="VBoxContainer" parent="margin"] +unique_name_in_owner = true +layout_mode = 2 + +[node name="HBoxContainer" type="HBoxContainer" parent="margin/Info"] +layout_mode = 2 + +[node name="InfoPanel" type="Panel" parent="margin/Info/HBoxContainer"] +custom_minimum_size = Vector2(222, 50) +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_styles/panel = SubResource("StyleBoxFlat_ctsm1") + +[node name="InfoLabel" type="Label" parent="margin/Info/HBoxContainer/InfoPanel"] +custom_minimum_size = Vector2(0, 42) +layout_mode = 0 +anchor_right = 1.0 +anchor_bottom = 1.0 +size_flags_horizontal = 3 +size_flags_vertical = 6 +text = "Aseprite Importer disabled. +Can be enabled in Editor Settings." + [connection signal="pressed" from="margin/Importer/Source/SourceButton" to="." method="_on_source_pressed"] [connection signal="pressed" from="margin/Importer/Source/RescanButton" to="." method="_on_rescan_pressed"] [connection signal="toggled" from="margin/Importer/TagsTitleBar/TagsTitle" to="." method="_on_options_title_toggled"] diff --git a/addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock_character.gd b/addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock_character.gd new file mode 100644 index 00000000..70a5750b --- /dev/null +++ b/addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock_character.gd @@ -0,0 +1,44 @@ +@tool +extends "res://addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock.gd" + +var _animation_player_path: String +var _animation_creator = preload("res://addons/popochiu/editor/importers/aseprite/animation_creator.gd").new() + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ GODOT ░░░░ +func _ready(): + if not target_node.has_node("AnimationPlayer"): + printerr(RESULT_CODE.get_error_message(RESULT_CODE.ERR_NO_ANIMATION_PLAYER_FOUND)) + return + + _animation_player_path = target_node.get_node("AnimationPlayer").get_path() + + # Instantiate animation creator + _animation_creator.init(config, _aseprite, file_system) + + super() + + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PRIVATE ░░░░ +func _on_import_pressed(): + # Set everything up + # This will populate _root_node and _options class variables + super() + + if _animation_player_path == "" or not _root_node.has_node(_animation_player_path): + _show_message("AnimationPlayer not found") + _importing = false + return + + var result = await _animation_creator.create_character_animations(target_node, _root_node.get_node(_animation_player_path), _options) + _importing = false + + if typeof(result) == TYPE_INT and result != RESULT_CODE.SUCCESS: + printerr(RESULT_CODE.get_error_message(result)) + _show_message("Some errors occurred. Please check output panel.", "Warning!") + else: + _show_message("%d animation tags processed." % [_tags_cache.size()], "Done!") + + +func _customize_tag_ui(tag_row: AnimationTagRow): + # Nothing special has to be done for Character tags + pass diff --git a/addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock_room.gd b/addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock_room.gd new file mode 100644 index 00000000..28bf2165 --- /dev/null +++ b/addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock_room.gd @@ -0,0 +1,121 @@ +@tool +extends "res://addons/popochiu/editor/importers/aseprite/docks/aseprite_importer_inspector_dock.gd" + +var _animation_creator = preload(\ +"res://addons/popochiu/editor/importers/aseprite/animation_creator.gd").new() + + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ GODOT ░░░░ +func _ready(): + # Instantiate animation creator + _animation_creator.init(config, _aseprite, file_system) + + super() + + +# ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PRIVATE ░░░░ +func _on_import_pressed(): + # Set everything up + # This will populate _root_node and _options class variables + super() + + var props_container = _root_node.get_node("Props") + var result: int = RESULT_CODE.SUCCESS + + # Create a prop for each tag that must be imported + # and populate it with the right sprite + for tag in _options.get("tags"): + # Ignore unwanted tags + if not tag.import: continue + + # Always convert to PascalCase as a standard + # TODO: check Godot 4 standards, I can't find info + var prop_name = tag.tag_name.to_pascal_case() + + # In case the prop is there, use the one we already have + var prop = props_container.get_node_or_null(prop_name) + if prop == null: + # Create a new prop if necessary, specifying the + # interaction flags. + prop = _create_prop(prop_name, tag.prop_clickable, tag.prop_visible) + else: + # Force flags (a bit redundant but they may have been changed + # in the Importer interface, for already imported props) + prop.clickable = tag.prop_clickable + prop.visible = tag.prop_visible + + prop.set_meta("ANIM_NAME", tag.tag_name) + + for prop in props_container.get_children(): + if not prop.has_meta("ANIM_NAME"): continue + # TODO: check if animation player exists in prop, if not add it + # same for Sprite2D even if it should be there... + + # Make the output folder match the prop's folder + _options.output_folder = prop.scene_file_path.get_base_dir() + + # Import a single tag animation + result = await _animation_creator.create_prop_animations( + prop, + prop.get_meta("ANIM_NAME"), + _options + ) + + for prop in props_container.get_children(): + if not prop.has_meta("ANIM_NAME"): continue + # Save the prop + result = await _save_prop(prop) + + # TODO: maybe check if this is better done with signals + _importing = false + + if typeof(result) == TYPE_INT and result != RESULT_CODE.SUCCESS: + printerr(RESULT_CODE.get_error_message(result)) + _show_message("Some errors occurred. Please check output panel.", "Warning!") + else: + await get_tree().create_timer(0.1).timeout + + # Once the popup is closed, call _clean_props() + _show_message( + "%d animation tags processed." % [_tags_cache.size()], + "Done!", + self, + "_clean_props" + ) + + +func _customize_tag_ui(tag_row: AnimationTagRow): + # Show props-related buttons if we are in a room + tag_row.show_prop_buttons() + + +func _create_prop(name: String, is_clickable: bool = true, is_visible: bool = true): + var factory = PopochiuPropFactory.new(main_dock) + if factory.create(name, _root_node, is_clickable, is_visible) != ResultCodes.SUCCESS: + return + + return factory.get_obj_scene() + +func _save_prop(prop: PopochiuProp): + var packed_scene: PackedScene = PackedScene.new() + packed_scene.pack(prop) + if ResourceSaver.save(packed_scene, prop.scene_file_path) != OK: + push_error( + "[Popochiu] Couldn't save animations for prop %s at %s" % + [prop.name, prop.scene_file_path] + ) + return ResultCodes.ERR_CANT_SAVE_OBJ_SCENE + return ResultCodes.SUCCESS + + +## Removes the script in all the created props so the properties (basically their +## texture) is not overwritten by the room's .tscn file. Then the scene is saved +## and reloaded so the nodes in the tree doesn't appear as Area2D but as the +## corresponding ones: PopochiuProp. +func _clean_props() -> void: + for prop in _root_node.get_node("Props").get_children(): + prop.set_script(null) + prop.remove_meta("ANIM_NAME") + + main_dock.ei.save_scene() + main_dock.ei.reload_scene_from_path(_root_node.scene_file_path) diff --git a/addons/popochiu/editor/inspector/aseprite_importer_inspector_plugin.gd b/addons/popochiu/editor/inspector/aseprite_importer_inspector_plugin.gd index fa07c125..8d44843c 100644 --- a/addons/popochiu/editor/inspector/aseprite_importer_inspector_plugin.gd +++ b/addons/popochiu/editor/inspector/aseprite_importer_inspector_plugin.gd @@ -1,30 +1,48 @@ extends EditorInspectorPlugin ## TODO: create a base class with pointer variables -const INSPECTOR_DOCK = preload("res://addons/popochiu/editor/importers/aseprite/docks/animation_player_inspector_dock.tscn") +const DOCKS_PATH := "res://addons/popochiu/editor/importers/aseprite/docks/" +const INSPECTOR_DOCK = preload(DOCKS_PATH + "aseprite_importer_inspector_dock.tscn") const CONFIG_SCRIPT = preload("res://addons/popochiu/editor/config/config.gd") +const INSPECTOR_DOCK_CHARACTER := DOCKS_PATH + "aseprite_importer_inspector_dock_character.gd" +const INSPECTOR_DOCK_ROOM := DOCKS_PATH + "aseprite_importer_inspector_dock_room.gd" + var ei: EditorInterface var fs: EditorFileSystem var config: RefCounted +var main_dock: Panel var _target_node: Node - + + # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ VIRTUAL ░░░░ func _can_handle(object): if object.has_method("get_parent") and object.get_parent() is Node2D: return false - return object is PopochiuCharacter #|| object is PopochiuInventoryItem || object is PopochiuProp + + return object is PopochiuCharacter || object is PopochiuRoom #|| object is PopochiuInventoryItem -func _parse_begin(object): +func _parse_begin(object: Object): _target_node = object -func _parse_category(object, category): - if category == 'Aseprite': - var dock = INSPECTOR_DOCK.instantiate() - dock.target_node = object - dock.config = config - dock.file_system = fs - add_custom_control(dock) func _parse_property(object, type, name, hint_type, hint_string, usage_flags, wide): - return name == 'popochiu_placeholder' + if name != 'popochiu_placeholder': return false + + # Instanciate and configure the dock + var dock = INSPECTOR_DOCK.instantiate() + + # Load the specific script in the dock + if object is PopochiuCharacter: + dock.set_script(load(INSPECTOR_DOCK_CHARACTER)) + if object is PopochiuRoom: + dock.set_script(load(INSPECTOR_DOCK_ROOM)) + + dock.target_node = object + dock.config = config + dock.file_system = fs + dock.main_dock = main_dock # TODO: change for SignalBus + + # Add the dock to the inspector + add_custom_control(dock) + return true diff --git a/addons/popochiu/editor/main_dock/popochiu_dock.gd b/addons/popochiu/editor/main_dock/popochiu_dock.gd index deb670e3..d261ac9e 100644 --- a/addons/popochiu/editor/main_dock/popochiu_dock.gd +++ b/addons/popochiu/editor/main_dock/popochiu_dock.gd @@ -55,7 +55,7 @@ var _rows_paths := [] path = INVENTORY_ITEMS_PATH, group = find_child('ItemsGroup'), popup = find_child('CreateInventoryItem'), - scene = INVENTORY_ITEMS_PATH + ('%s/item_%s.tscn') + scene = INVENTORY_ITEMS_PATH + ('%s/inventory_item_%s.tscn') }, Constants.Types.DIALOG: { path = DIALOGS_PATH, @@ -261,6 +261,10 @@ func get_opened_room() -> PopochiuRoom: return _tab_room.opened_room +func get_opened_room_tab() -> VBoxContainer: + return _tab_room + + func open_setup() -> void: setup_dialog.appear() diff --git a/addons/popochiu/editor/main_dock/tab_room.gd b/addons/popochiu/editor/main_dock/tab_room.gd index 02202d5f..46e6dc6f 100644 --- a/addons/popochiu/editor/main_dock/tab_room.gd +++ b/addons/popochiu/editor/main_dock/tab_room.gd @@ -1,5 +1,6 @@ @tool extends VBoxContainer + # Handles the Room tab in Popochiu's dock # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ @@ -121,7 +122,6 @@ func scene_changed(scene_root: Node) -> void: PopochiuUtils.print_error( "This room doesn't have a [code]script_name[/code] value!" ) - return if not scene_root is PopochiuRoom: diff --git a/addons/popochiu/editor/popups/create_character/create_character.gd b/addons/popochiu/editor/popups/create_character/create_character.gd index a99673a8..bc25f20d 100644 --- a/addons/popochiu/editor/popups/create_character/create_character.gd +++ b/addons/popochiu/editor/popups/create_character/create_character.gd @@ -9,21 +9,12 @@ @tool extends 'res://addons/popochiu/editor/popups/creation_popup.gd' -const CHARACTER_STATE_TEMPLATE :=\ -'res://addons/popochiu/engine/templates/character_state_template.gd' -const CHARACTER_SCRIPT_TEMPLATE :=\ -'res://addons/popochiu/engine/templates/character_template.gd' -const CHARACTER_SCENE :=\ -'res://addons/popochiu/engine/objects/character/popochiu_character.tscn' -const Constants := preload('res://addons/popochiu/popochiu_resources.gd') -const PopochiuDock :=\ -preload('res://addons/popochiu/editor/main_dock/popochiu_dock.gd') +# TODO: Giving a proper class name to PopochiuDock eliminates the need to preload it +# and to cast it as the right type later in code. +const PopochiuDock := preload('res://addons/popochiu/editor/main_dock/popochiu_dock.gd') var _new_character_name := '' -var _new_character_path := '' -var _character_path_template := '' -var _pascal_name := '' - +var _factory: PopochiuCharacterFactory # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ GODOT ░░░░ func _ready() -> void: @@ -37,133 +28,22 @@ func _create() -> void: _error_feedback.show() return - # TODO: Check that there is not a character in the same PATH. - # TODO: Delete created files if creation is not complete. - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the folder for the character - DirAccess.make_dir_absolute( - (_main_dock as PopochiuDock).CHARACTERS_PATH + _new_character_name - ) - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the state Resource for the character and a script so devs - # can add extra properties to that state - var state_template: Script = load(CHARACTER_STATE_TEMPLATE).duplicate() - if ResourceSaver.save( - state_template, _new_character_path + '_state.gd' - ) != OK: - push_error('[Popochiu] Could not create character state script: %s' %\ - _new_character_name) - # TODO: Show feedback in the popup - return - - var character_resource: PopochiuCharacterData =\ - load(_new_character_path + '_state.gd').new() - character_resource.script_name = _pascal_name - character_resource.scene = _new_character_path + '.tscn' - character_resource.resource_name = _pascal_name - - if ResourceSaver.save( - character_resource, _new_character_path + '.tres' - ) != OK: - push_error( - "[Popochiu] Couldn't create PopochiuCharacterData for: %s" %\ - _new_character_name - ) - # TODO: Show feedback in the popup - return + # Setup the prop helper and use it to create the prop + _factory = PopochiuCharacterFactory.new(_main_dock) - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the script for the character - var character_script: Script = load(CHARACTER_SCRIPT_TEMPLATE).duplicate() - var new_code := character_script.source_code - - character_script.source_code = '' - - if ResourceSaver.save(character_script, _new_character_path + '.gd') != OK: - push_error( - '[Popochiu] Could not create script: %s.gd' % _new_character_name - ) - # TODO: Show feedback in the popup - return - - new_code = new_code.replace( - 'character_state_template', - 'character_%s_state' % _new_character_name - ) - - new_code = new_code.replace( - 'Data = null', - "Data = load('%s.tres')" % _new_character_path - ) - - character_script = load(_new_character_path + '.gd') - character_script.source_code = new_code - - if ResourceSaver.save(character_script, _new_character_path + '.gd') != OK: - push_error( - '[Popochiu] Could not update script: %s.gd' % _new_character_name - ) - # TODO: Show feedback in the popup + if _factory.create(_new_character_name) != ResultCodes.SUCCESS: + # TODO: show a message in the popup! return - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the Character instance - var new_character: PopochiuCharacter = load(CHARACTER_SCENE).instantiate() - # The script is assigned first so that other properties will not be - # overwritten by that assignment. - new_character.set_script(load(_new_character_path + '.gd')) - - new_character.name = 'Character' + _pascal_name - new_character.script_name = _pascal_name - new_character.description = _new_character_name.capitalize() - new_character.cursor = Constants.CURSOR_TYPE.TALK - - if PopochiuResources.get_settings().is_pixel_art_game: - new_character.get_node("Sprite2D").texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Save the character scene (.tscn) - var new_character_packed_scene: PackedScene = PackedScene.new() - new_character_packed_scene.pack(new_character) - if ResourceSaver.save( - new_character_packed_scene, _new_character_path + '.tscn' - ) != OK: - push_error( - "[Popochiu] Couldn't create character: %s.tscn" % _new_character_name - ) - # TODO: Show feedback in the popup - return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Add the created character to Popochiu's characters list - if _main_dock.add_resource_to_popochiu( - 'characters', ResourceLoader.load(_new_character_path + '.tres') - ) != OK: - push_error( - "[Popochiu] Couldn't add the created character to Popochiu: %s" %\ - _new_character_name - ) - # TODO: Show feedback in the popup - return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Add the character to the C singleton - PopochiuResources.update_autoloads(true) - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Update the list of characters in the dock - (_main_dock as PopochiuDock).add_to_list( - Constants.Types.CHARACTER, _pascal_name - ) + var character_scene = _factory.get_obj_scene() # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ # Open the scene in the editor await get_tree().create_timer(0.1).timeout - _main_dock.ei.select_file(_new_character_path + '.tscn') - _main_dock.ei.open_scene_from_path(_new_character_path + '.tscn') + _main_dock.ei.select_file(character_scene.scene_file_path) + _main_dock.ei.open_scene_from_path(character_scene.scene_file_path) # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ # That's all!!! @@ -172,7 +52,6 @@ func _create() -> void: func _clear_fields() -> void: _new_character_name = '' - _new_character_path = '' # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ SET & GET ░░░░ @@ -181,9 +60,6 @@ func set_main_dock(node: Panel) -> void: if not _main_dock: return - # res://popochiu/characters - _character_path_template = _main_dock.CHARACTERS_PATH + '%s/character_%s' - # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PRIVATE ░░░░ func _update_name(new_text: String) -> void: @@ -191,9 +67,6 @@ func _update_name(new_text: String) -> void: if _name: _new_character_name = _name.to_snake_case() - _pascal_name = _name - _new_character_path = _character_path_template %\ - [_new_character_name, _new_character_name] _info.text = ( 'In [b]%s[/b] the following files will be created:\ diff --git a/addons/popochiu/editor/popups/create_dialog/create_dialog.gd b/addons/popochiu/editor/popups/create_dialog/create_dialog.gd index 4bcc872f..0cc6d87b 100644 --- a/addons/popochiu/editor/popups/create_dialog/create_dialog.gd +++ b/addons/popochiu/editor/popups/create_dialog/create_dialog.gd @@ -7,16 +7,12 @@ @tool extends 'res://addons/popochiu/editor/popups/creation_popup.gd' -const DIALOG_SCRIPT_TEMPLATE :=\ -'res://addons/popochiu/engine/templates/dialog_template.gd' -const Constants := preload('res://addons/popochiu/popochiu_resources.gd') -const PopochiuDock :=\ -preload('res://addons/popochiu/editor/main_dock/popochiu_dock.gd') +# TODO: Giving a proper class name to PopochiuDock eliminates the need to preload it +# and to cast it as the right type later in code. +const PopochiuDock := preload('res://addons/popochiu/editor/main_dock/popochiu_dock.gd') var _new_dialog_name := '' -var _new_dialog_path := '' -var _dialog_path_template := '' -var _pascal_name := '' +var _factory: PopochiuDialogFactory # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ GODOT ░░░░ @@ -31,64 +27,21 @@ func _create() -> void: _error_feedback.show() return - # TODO: Check if there is not already a dialog on the same PATH. - # TODO: Delete already created files if something breaks before finishing. - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the directory where the new dialog will be saved - DirAccess.make_dir_absolute(_main_dock.DIALOGS_PATH + _new_dialog_name) - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the script of the new dialog - var dialog_template := load(DIALOG_SCRIPT_TEMPLATE).duplicate() - - if ResourceSaver.save(dialog_template, _new_dialog_path + '.gd') != OK: - push_error( - "[Popochiu] Couldn't create script: %s.gd" % _new_dialog_name - ) - # TODO: Show feedback in the popup - return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create dialog Resource - var dialog_resource := PopochiuDialog.new() - dialog_resource.set_script(load(_new_dialog_path + '.gd')) - - dialog_resource.script_name = _pascal_name - dialog_resource.resource_name = _new_dialog_name - - if ResourceSaver.save(dialog_resource, _new_dialog_path + '.tres') != OK: - push_error("[Popochiu] Couldn't create dialog: %s" %_new_dialog_name) - # TODO: Show feedback in the popup - return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Add the dialog to Popochiu - if _main_dock.add_resource_to_popochiu( - 'dialogs', ResourceLoader.load(_new_dialog_path + '.tres') - ) != OK: - push_error( - "[Popochiu] Couldn't add the created dialog to Popochiu: %s" %\ - _new_dialog_name - ) - # TODO: Show feedback in the popup + # Setup the prop helper and use it to create the prop + _factory = PopochiuDialogFactory.new(_main_dock) + + if _factory.create(_new_dialog_name) != ResultCodes.SUCCESS: + # TODO: show a message in the popup! return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Add the dialog to the D singleton - PopochiuResources.update_autoloads(true) - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Update the list of dialogs in the Dock - (_main_dock as PopochiuDock).add_to_list( - Constants.Types.DIALOG, _pascal_name - ) - + + var dialog_resource = _factory.get_obj_resource() + # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ # Open dialog in the Inspector await get_tree().create_timer(0.1).timeout - _main_dock.ei.select_file(_new_dialog_path + '.tres') - _main_dock.ei.edit_resource(load(_new_dialog_path + '.tres')) + _main_dock.ei.select_file(dialog_resource.resource_path) + _main_dock.ei.edit_resource(load(dialog_resource.resource_path)) # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ # The end @@ -97,7 +50,6 @@ func _create() -> void: func _clear_fields() -> void: _new_dialog_name = '' - _new_dialog_path = '' # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ SET & GET ░░░░ @@ -105,9 +57,6 @@ func set_main_dock(node: Panel) -> void: super(node) if not _main_dock: return - - # res://popochiu/dialogs - _dialog_path_template = _main_dock.DIALOGS_PATH + '%s/dialog_%s' # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PRIVATE ░░░░ @@ -116,9 +65,6 @@ func _update_name(new_text: String) -> void: if _name: _new_dialog_name = _name.to_snake_case() - _pascal_name = _name - _new_dialog_path = _dialog_path_template %\ - [_new_dialog_name, _new_dialog_name] _info.text = ( 'In [b]%s[/b] the following files will be created:\ diff --git a/addons/popochiu/editor/popups/create_hotspot/create_hotspot.gd b/addons/popochiu/editor/popups/create_hotspot/create_hotspot.gd index fcc356a6..67271ecf 100644 --- a/addons/popochiu/editor/popups/create_hotspot/create_hotspot.gd +++ b/addons/popochiu/editor/popups/create_hotspot/create_hotspot.gd @@ -1,23 +1,13 @@ +# Creates a new hotspot in the room. @tool extends 'res://addons/popochiu/editor/popups/creation_popup.gd' -# Permite crear un nuevo Hotspot para una habitación. - -const SCRIPT_TEMPLATE :=\ -'res://addons/popochiu/engine/templates/hotspot_template.gd' -const HOTSPOT_SCENE :=\ -'res://addons/popochiu/engine/objects/hotspot/popochiu_hotspot.tscn' -const Constants := preload('res://addons/popochiu/popochiu_resources.gd') -const TabRoom := preload("res://addons/popochiu/editor/main_dock/tab_room.gd") +## TODO: remove this legacy... var room_tab: VBoxContainer = null var _room: Node2D = null var _new_hotspot_name := '' -var _new_hotspot_path := '' -var _hotspot_path_template := '' -var _room_path := '' -var _room_dir := '' -var _pascal_name := '' +var _factory: PopochiuHotspotFactory # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ GODOT ░░░░ @@ -28,79 +18,35 @@ func _ready() -> void: # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ VIRTUAL ░░░░ func _create() -> void: - if _new_hotspot_name.is_empty(): - _error_feedback.show() - return - - # TODO: Check if another Hotspot was created in the same PATH. - # TODO: Remove created files if the creation process failed. - var script_path := _new_hotspot_path + '.gd' - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the folder for the Hotspot - assert( - DirAccess.make_dir_recursive_absolute( - _new_hotspot_path.get_base_dir() - ) == OK, - '[Popochiu] Could not create Hotspot folder for ' + _new_hotspot_name - ) - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Crear el script de el hotspot (si tiene interacción) - var hotspot_template := load(SCRIPT_TEMPLATE) - if ResourceSaver.save(hotspot_template, script_path) != OK: - push_error('[Popochiu] Could not create: %s.gd' % _new_hotspot_name) - # TODO: Show feedback in the popup + # Setup the region helper and use it to create the hotspot + _factory = PopochiuHotspotFactory.new(_main_dock) + + if _factory.create(_new_hotspot_name, _room) != ResultCodes.SUCCESS: + # TODO: show a message in the popup! return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Crear el hotspot a agregar a la habitación - var hotspot: PopochiuHotspot = ResourceLoader.load(HOTSPOT_SCENE).instantiate() - hotspot.set_script(ResourceLoader.load(script_path)) - hotspot.name = _pascal_name - hotspot.script_name = _pascal_name - hotspot.description = _new_hotspot_name.capitalize() - hotspot.cursor = Constants.CURSOR_TYPE.ACTIVE - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Agregar el hotspot a su habitación - _room.get_node('Hotspots').add_child(hotspot) - - hotspot.owner = _room - hotspot.position = Vector2( - ProjectSettings.get_setting(PopochiuResources.DISPLAY_WIDTH), - ProjectSettings.get_setting(PopochiuResources.DISPLAY_HEIGHT) - ) / 2.0 - - var collision := CollisionPolygon2D.new() - collision.name = 'InteractionPolygon' - hotspot.add_child(collision) - collision.owner = _room - collision.modulate = Color.BLUE - - _main_dock.ei.save_scene() - + + var hotspot = _factory.get_obj_scene() + # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Abrir las propiedades del hotspot creado en el Inspector + # Open the properties of the created region in the inspector + # Done here because the creation is interactive in this case await get_tree().create_timer(0.1).timeout PopochiuEditorHelper.select_node(hotspot) # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Fin + # End hide() + func _clear_fields() -> void: _new_hotspot_name = '' - _new_hotspot_path = '' # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░ func room_opened(r: Node2D) -> void: _room = r - _room_path = _room.scene_file_path - _room_dir = _room_path.get_base_dir() - _hotspot_path_template = _room_dir + '/hotspots/%s/hotspot_%s' # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PRIVATE ░░░░ @@ -109,14 +55,11 @@ func _update_name(new_text: String) -> void: if _name: _new_hotspot_name = _name.to_snake_case() - _pascal_name = _name - _new_hotspot_path = _hotspot_path_template %\ - [_new_hotspot_name, _new_hotspot_name] _info.text = ( 'In [b]%s[/b] the following file will be created:\n[code]%s[/code]'\ % [ - _new_hotspot_path.get_base_dir(), + _room.scene_file_path.get_base_dir() + '/hotspots', 'hotspot_' + _new_hotspot_name + '.gd' ] ) diff --git a/addons/popochiu/editor/popups/create_inventory_item/create_inventory_item.gd b/addons/popochiu/editor/popups/create_inventory_item/create_inventory_item.gd index 8af5e613..b90ff147 100644 --- a/addons/popochiu/editor/popups/create_inventory_item/create_inventory_item.gd +++ b/addons/popochiu/editor/popups/create_inventory_item/create_inventory_item.gd @@ -9,20 +9,12 @@ @tool extends 'res://addons/popochiu/editor/popups/creation_popup.gd' -const INVENTORY_ITEM_STATE_TEMPLATE :=\ -'res://addons/popochiu/engine/templates/inventory_item_state_template.gd' -const INVENTORY_ITEM_SCRIPT_TEMPLATE := \ -'res://addons/popochiu/engine/templates/inventory_item_template.gd' -const BASE_INVENTORY_ITEM_PATH := \ -'res://addons/popochiu/engine/objects/inventory_item/popochiu_inventory_item.tscn' -const Constants := preload('res://addons/popochiu/popochiu_resources.gd') -const PopochiuDock :=\ -preload('res://addons/popochiu/editor/main_dock/popochiu_dock.gd') +# TODO: Giving a proper class name to PopochiuDock eliminates the need to preload it +# and to cast it as the right type later in code. +const PopochiuDock := preload('res://addons/popochiu/editor/main_dock/popochiu_dock.gd') var _new_item_name := '' -var _new_item_path := '' -var _item_path_template := '' -var _pascal_name := '' +var _factory: PopochiuInventoryItemFactory # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ GODOT ░░░░ @@ -37,122 +29,20 @@ func _create() -> void: _error_feedback.show() return - # TODO: Check that there is not an inventory item in the same PATH. - # TODO: Delete created files if something fails. - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the folder for the item - DirAccess.make_dir_absolute(_main_dock.INVENTORY_ITEMS_PATH + _new_item_name) - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the state Resource for the item and a script so devs - # can add extra properties to that state - var state_template: Script = load(INVENTORY_ITEM_STATE_TEMPLATE).duplicate() - if ResourceSaver.save(state_template, _new_item_path + '_state.gd') != OK: - push_error( - "[Popochiu] Couldn't create item state script: %s" %\ - _new_item_name - ) - # TODO: Show feedback in the popup - return - - var item_resource: PopochiuInventoryItemData =\ - load(_new_item_path + '_state.gd').new() - item_resource.script_name = _pascal_name - item_resource.scene = _new_item_path + '.tscn' - item_resource.resource_name = _pascal_name - - if ResourceSaver.save(item_resource, _new_item_path + '.tres') != OK: - push_error( - "[Popochiu] Couldn't create PopochiuInventoryItemData for item: %s"\ - % _new_item_name - ) - # TODO: Show feedback in the popup - return - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the script for the item - var item_script: Script = load(INVENTORY_ITEM_SCRIPT_TEMPLATE).duplicate() - var new_code := item_script.source_code - - item_script.source_code = '' - - if ResourceSaver.save(item_script, _new_item_path + '.gd') != OK: - push_error('[Popochiu] Could not create script: %s.gd' % _new_item_name) - # TODO: Show feedback in the popup - return - - new_code = new_code.replace( - 'inventory_item_state_template', - 'item_%s_state' % _new_item_name - ) - - new_code = new_code.replace( - 'Data = null', - "Data = load('%s.tres')" % _new_item_path - ) - - item_script = load(_new_item_path + '.gd') - item_script.source_code = new_code - - if ResourceSaver.save(item_script, _new_item_path + '.gd') != OK: - push_error('[Popochiu] Could not update script: %s.gd' % _new_item_name) - # TODO: Show feedback in the popup - return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the item instance - var new_item: PopochiuInventoryItem =\ - preload(BASE_INVENTORY_ITEM_PATH).instantiate() - # The script is assigned first so that other properties will not be - # overwritten by that assignment. - new_item.set_script(load(_new_item_path + '.gd')) - - new_item.name = 'Item' + _pascal_name - new_item.script_name = _pascal_name - new_item.description = _pascal_name.capitalize() - new_item.cursor = Constants.CURSOR_TYPE.USE - new_item.size_flags_vertical = new_item.SIZE_SHRINK_CENTER - - if PopochiuResources.get_settings().is_pixel_art_game: - new_item.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Save the item scene (.tscn) - var new_item_packed_scene: PackedScene = PackedScene.new() - new_item_packed_scene.pack(new_item) - if ResourceSaver.save(new_item_packed_scene, _new_item_path + '.tscn') != OK: - push_error('[Popochiu] Could not create item: %s.tscn' % _new_item_name) - # TODO: Show feedback in the popup - return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Add the created inventory item to Popochiu's inventory_items list - if _main_dock.add_resource_to_popochiu( - 'inventory_items', ResourceLoader.load(_new_item_path + '.tres') - ) != OK: - push_error( - "[Popochiu] Couldn't add the created inventory item to Popochiu: %s"\ - % _new_item_name - ) - # TODO: Show feedback in the popup + # Setup the prop helper and use it to create the prop + _factory = PopochiuInventoryItemFactory.new(_main_dock) + + if _factory.create(_new_item_name) != ResultCodes.SUCCESS: + # TODO: show a message in the popup! return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Add the inventory item to the I singleton - PopochiuResources.update_autoloads(true) - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Update the list of inventory items in the dock - (_main_dock as PopochiuDock).add_to_list( - Constants.Types.INVENTORY_ITEM, _pascal_name - ) + var item_scene = _factory.get_obj_scene() # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ # Open the scene in the editor await get_tree().create_timer(0.1).timeout - _main_dock.ei.select_file(_new_item_path + '.tscn') - _main_dock.ei.open_scene_from_path(_new_item_path + '.tscn') + _main_dock.ei.select_file(item_scene.scene_file_path) + _main_dock.ei.open_scene_from_path(item_scene.scene_file_path) # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ # That's all!!!!!!! @@ -161,7 +51,6 @@ func _create() -> void: func _clear_fields() -> void: _new_item_name = '' - _new_item_path = '' # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ SET & GET ░░░░ @@ -170,9 +59,6 @@ func set_main_dock(node: Panel) -> void: if not _main_dock: return - # res://popochiu/inventory_items/ - _item_path_template = _main_dock.INVENTORY_ITEMS_PATH + '%s/item_%s' - # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PRIVATE ░░░░ func _update_name(new_text: String) -> void: @@ -180,18 +66,15 @@ func _update_name(new_text: String) -> void: if _name: _new_item_name = _name.to_snake_case() - _pascal_name = _name - _new_item_path = _item_path_template %\ - [_new_item_name, _new_item_name] _info.text = ( 'In [b]%s[/b] the following files will be created:\ \n[code]%s, %s and %s[/code]' \ % [ _main_dock.INVENTORY_ITEMS_PATH + _new_item_name, - 'item_' + _new_item_name + '.tscn', - 'item_' + _new_item_name + '.gd', - 'item_' + _new_item_name + '.tres' + 'inventory_item_' + _new_item_name + '.tscn', + 'inventory_item_' + _new_item_name + '.gd', + 'inventory_item_' + _new_item_name + '.tres' ] ) _info.show() diff --git a/addons/popochiu/editor/popups/create_prop/create_prop.gd b/addons/popochiu/editor/popups/create_prop/create_prop.gd index d41b136d..1b0e644e 100644 --- a/addons/popochiu/editor/popups/create_prop/create_prop.gd +++ b/addons/popochiu/editor/popups/create_prop/create_prop.gd @@ -5,22 +5,15 @@ @tool extends 'res://addons/popochiu/editor/popups/creation_popup.gd' -const PROP_SCRIPT_TEMPLATE :=\ -'res://addons/popochiu/engine/templates/prop_template.gd' -const BASE_PROP_PATH :=\ -'res://addons/popochiu/engine/objects/prop/popochiu_prop.tscn' -const Constants := preload('res://addons/popochiu/popochiu_resources.gd') -const TabRoom := preload("res://addons/popochiu/editor/main_dock/tab_room.gd") +## TODO: remove this legacy... +#const TabRoom := preload("res://addons/popochiu/editor/main_dock/tab_room.gd") +## TODO: remove this legacy... var room_tab: VBoxContainer = null var _room: Node2D = null var _new_prop_name := '' -var _new_prop_path := '' -var _prop_path_template := '' -var _room_path := '' -var _room_dir := '' -var _pascal_name := '' +var _factory: PopochiuPropFactory @onready var _interaction_checkbox: CheckBox = find_child('InteractionCheckbox') @@ -38,108 +31,41 @@ func _create() -> void: if _new_prop_name.is_empty(): _error_feedback.show() return - - # TODO: Check if another Prop was created in the same PATH. - # TODO: Remove created files if the creation process failed. - var script_path := _new_prop_path + '.gd' - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the folder for the Prop - assert( - DirAccess.make_dir_recursive_absolute(_new_prop_path.get_base_dir()) == OK, - '[Popochiu] Could not create prop folder for ' + _new_prop_name - ) - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the script for the prop (if it has interaction) - if _interaction_checkbox.button_pressed: - var prop_template := load(PROP_SCRIPT_TEMPLATE) - - if ResourceSaver.save(prop_template, script_path) != OK: - push_error( - "[Popochiu] Couldn't create script: %s.gd" % _new_prop_name - ) - # TODO: Show feedback in the popup - return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the prop - var prop: PopochiuProp = ResourceLoader.load(BASE_PROP_PATH).instantiate() - - if _interaction_checkbox.button_pressed: - prop.set_script(ResourceLoader.load(script_path)) - - prop.name = _pascal_name - prop.script_name = _pascal_name - prop.description = _new_prop_name.capitalize() - prop.clickable = _interaction_checkbox.button_pressed - prop.cursor = Constants.CURSOR_TYPE.ACTIVE - - if PopochiuResources.get_settings().is_pixel_art_game: - prop.get_node("Sprite2D").texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST - - if _new_prop_name in ['bg', 'background']: - prop.baseline =\ - -ProjectSettings.get_setting(PopochiuResources.DISPLAY_HEIGHT) / 2.0 - prop.z_index = -1 - + # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Save the prop scene (.tscn) - var prop_packed_scene: PackedScene = PackedScene.new() - prop_packed_scene.pack(prop) - if ResourceSaver.save( - prop_packed_scene, _new_prop_path + '.tscn' - ) != OK: - push_error("[Popochiu] Couldn't create prop: %s.tscn" % _new_prop_name) - # TODO: Show feedback in the popup + # Setup the prop helper and use it to create the prop + _factory = PopochiuPropFactory.new(_main_dock) + + if _factory.create( + _new_prop_name, _room, + _interaction_checkbox.button_pressed + ) != ResultCodes.SUCCESS: + # TODO: show a message in the popup! return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Add the prop to its room - # Instancing the created .tscn file fixes #58 - var prop_instance: PopochiuProp = load(_new_prop_path + '.tscn').instantiate() - - _room.get_node('Props').add_child(prop_instance) - - prop_instance.owner = _room - prop_instance.position = Vector2( - ProjectSettings.get_setting(PopochiuResources.DISPLAY_WIDTH), - ProjectSettings.get_setting(PopochiuResources.DISPLAY_HEIGHT) - ) / 2.0 - - if _interaction_checkbox.button_pressed: - var collision := CollisionPolygon2D.new() - collision.name = 'InteractionPolygon' - - prop_instance.add_child(collision) - collision.owner = _room - - _main_dock.ei.save_scene() - + + var prop_instance = _factory.get_obj_scene() + # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Abrir las propiedades de la prop creada en el Inspector + # Open the properties of the created prop in the inspector + # Done here because the creation is interactive in this case _main_dock.fs.scan() await get_tree().create_timer(0.1).timeout - PopochiuEditorHelper.select_node(prop_instance) - _main_dock.ei.select_file(_new_prop_path + '.tscn') + _main_dock.ei.edit_node(prop_instance) + _main_dock.ei.select_file(prop_instance.scene_file_path) # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Fin + # End hide() func _clear_fields() -> void: _new_prop_name = '' - _new_prop_path = '' _interaction_checkbox.button_pressed = false # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░ func room_opened(r: Node2D) -> void: _room = r - _room_path = _room.scene_file_path - _room_dir = _room_path.get_base_dir() - _prop_path_template = _room_dir + '/props/%s/prop_%s' # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PRIVATE ░░░░ @@ -148,8 +74,6 @@ func _update_name(new_text: String) -> void: if _name: _new_prop_name = _name.to_snake_case() - _pascal_name = _name - _new_prop_path = _prop_path_template % [_new_prop_name, _new_prop_name] _update_info() _info.show() @@ -169,7 +93,7 @@ func _update_info() -> void: _info.text = ( 'In [b]%s[/b] the following file will be created:\n[code]%s[/code]' \ % [ - _new_prop_path.get_base_dir(), + _room.scene_file_path.get_base_dir() + '/props', 'prop_' + _new_prop_name + '.gd' ] ) diff --git a/addons/popochiu/editor/popups/create_region/create_region.gd b/addons/popochiu/editor/popups/create_region/create_region.gd index 7a981a37..6257d779 100644 --- a/addons/popochiu/editor/popups/create_region/create_region.gd +++ b/addons/popochiu/editor/popups/create_region/create_region.gd @@ -1,20 +1,14 @@ +# Allows you to create a new Region for a room. + @tool extends 'res://addons/popochiu/editor/popups/creation_popup.gd' -# Permite crear una nueva Region para una habitación. - -const SCRIPT_TEMPLATE := 'res://addons/popochiu/engine/templates/region_template.gd' -const REGION_SCENE := 'res://addons/popochiu/engine/objects/region/popochiu_region.tscn' -const Constants := preload('res://addons/popochiu/popochiu_resources.gd') +## TODO: remove this legacy... var room_tab: VBoxContainer = null var _room: Node2D = null var _new_region_name := '' -var _new_region_path := '' -var _region_path_template := '' -var _room_path := '' -var _room_dir := '' -var _pascal_name := '' +var _factory: PopochiuRegionFactory # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ GODOT ░░░░ @@ -29,75 +23,34 @@ func _create() -> void: _error_feedback.show() return - # TODO: Check if another Region was created in the same PATH. - # TODO: Remove created files if the creation process failed. - var script_path := _new_region_path + '.gd' - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the folder for the Region - assert( - DirAccess.make_dir_recursive_absolute( - _new_region_path.get_base_dir() - ) == OK, - '[Popochiu] Could not create region folder for ' + _new_region_name - ) - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Crear el script de la región - var region_template := load(SCRIPT_TEMPLATE) - if ResourceSaver.save(region_template, script_path) != OK: - push_error( - "[Popochiu] Couldn't create script: %s.gd" % _new_region_name - ) - # TODO: Show feedback in the popup + # Setup the region helper and use it to create the region + _factory = PopochiuRegionFactory.new(_main_dock) + + if _factory.create(_new_region_name, _room) != ResultCodes.SUCCESS: + # TODO: show a message in the popup! return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Crear la región a agregar a la habitación - var region: PopochiuRegion = ResourceLoader.load(REGION_SCENE).instantiate() - region.set_script(ResourceLoader.load(script_path)) - region.name = _pascal_name - region.script_name = _pascal_name - region.description = _new_region_name.capitalize() - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Agregar la región a su habitación - _room.get_node('Regions').add_child(region) - region.owner = _room - region.position = Vector2( - ProjectSettings.get_setting(PopochiuResources.DISPLAY_WIDTH), - ProjectSettings.get_setting(PopochiuResources.DISPLAY_HEIGHT) - ) / 2.0 - - var collision := CollisionPolygon2D.new() - collision.name = 'InteractionPolygon' - region.add_child(collision) - collision.owner = _room - collision.modulate = Color.CYAN - - _main_dock.ei.save_scene() - + + var region = _factory.get_obj_scene() + # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Abrir las propiedades de la región creada en el Inspector + # Open the properties of the created region in the inspector + # Done here because the creation is interactive in this case await get_tree().create_timer(0.1).timeout PopochiuEditorHelper.select_node(region) # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Fin + # End hide() func _clear_fields() -> void: _new_region_name = '' - _new_region_path = '' # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ VIRTUAL ░░░░ func room_opened(r: Node2D) -> void: _room = r - _room_path = _room.scene_file_path - _room_dir = _room_path.get_base_dir() - _region_path_template = _room_dir + '/regions/%s/region_%s' # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PRIVATE ░░░░ @@ -106,14 +59,10 @@ func _update_name(new_text: String) -> void: if _name: _new_region_name = _name.to_snake_case() - _pascal_name = _name - _new_region_path = _region_path_template %\ - [_new_region_name, _new_region_name] - _info.text = ( 'In [b]%s[/b] the following files will be created:\n[code]%s[/code]' \ % [ - _room_dir + '/regions', + _room.scene_file_path.get_base_dir() + '/regions', 'region_' + _new_region_name + '.gd' ] ) diff --git a/addons/popochiu/editor/popups/create_room/create_room.gd b/addons/popochiu/editor/popups/create_room/create_room.gd index 88e5622b..fb5b08e8 100644 --- a/addons/popochiu/editor/popups/create_room/create_room.gd +++ b/addons/popochiu/editor/popups/create_room/create_room.gd @@ -9,22 +9,14 @@ @tool extends 'res://addons/popochiu/editor/popups/creation_popup.gd' -const ROOM_STATE_TEMPLATE :=\ -'res://addons/popochiu/engine/templates/room_state_template.gd' -const ROOM_SCRIPT_TEMPLATE :=\ -'res://addons/popochiu/engine/templates/room_template.gd' -const BASE_ROOM_PATH :=\ -'res://addons/popochiu/engine/objects/room/popochiu_room.tscn' -const Constants := preload('res://addons/popochiu/popochiu_resources.gd') -const PopochiuDock :=\ -preload('res://addons/popochiu/editor/main_dock/popochiu_dock.gd') +# TODO: Giving a proper class name to PopochiuDock eliminates the need to preload it +# and to cast it as the right type later in code. +const PopochiuDock := preload('res://addons/popochiu/editor/main_dock/popochiu_dock.gd') var show_set_as_main := false : set = _set_show_set_as_main -var _room_name := '' -var _room_path := '' -var _room_path_template := '' -var _pascal_name := '' +var _new_room_name := '' +var _factory: PopochiuRoomFactory @onready var _set_as_main: PanelContainer = find_child('SetAsMainContainer') @onready var _set_as_main_check: CheckBox = _set_as_main.find_child('CheckBox') @@ -50,125 +42,28 @@ func _ready() -> void: # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ VIRTUAL ░░░░ func _create() -> void: - if _room_name.is_empty(): + if _new_room_name.is_empty(): _error_feedback.show() return - # TODO: Check that there is not a room in the same PATH. - # TODO: Delete created files if creation is not complete. - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the folder for the room - DirAccess.make_dir_absolute(_main_dock.ROOMS_PATH + _room_name) - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the state Resource for the room and a script so devs can add extra - # properties to that state - var state_template: Script = load(ROOM_STATE_TEMPLATE).duplicate() - if ResourceSaver.save(state_template, _room_path + '_state.gd') != OK: - push_error('[Popochiu] Could not create room state script: %s' %\ - _room_name) - # TODO: Show feedback in the popup - return - - var room_resource: PopochiuRoomData = load(_room_path + '_state.gd').new() - room_resource.script_name = _pascal_name - room_resource.scene = _room_path + '.tscn' - room_resource.resource_name = _pascal_name - - if ResourceSaver.save(room_resource, _room_path + '.tres') != OK: - push_error( - "[Popochiu] Couldn't create PopochiuRoomData for room: %s" %\ - _room_name - ) - # TODO: Show feedback in the popup - return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the script for the room - var room_script: Script = load(ROOM_SCRIPT_TEMPLATE).duplicate() - var new_code := room_script.source_code - - room_script.source_code = '' - - if ResourceSaver.save(room_script, _room_path + '.gd') != OK: - push_error("[Popochiu] Couldn't create script: %s" % _room_name) - # TODO: Show feedback in the popup - return - - new_code = new_code.replace( - 'room_state_template', - 'room_%s_state' % _room_name - ) - - new_code = new_code.replace( - 'Data = null', - "Data = load('%s.tres')" % _room_path - ) - - room_script = load(_room_path + '.gd') - room_script.source_code = new_code - - if ResourceSaver.save(room_script, _room_path + '.gd') != OK: - push_error('[Popochiu] Could not update script: %s' %\ - _room_name) - # TODO: Show feedback in the popup - return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the room instance - var new_room: PopochiuRoom = load(BASE_ROOM_PATH).instantiate() - - # The script is assigned first so that other properties will not be - # overwritten by that assignment. - new_room.set_script(load(_room_path + '.gd')) - - new_room.name = 'Room' + _pascal_name - new_room.script_name = _pascal_name - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Save the room scene (.tscn) - var new_room_packed_scene: PackedScene = PackedScene.new() - new_room_packed_scene.pack(new_room) - if ResourceSaver.save(new_room_packed_scene, _room_path + '.tscn') != OK: - push_error("[Popochiu] Couldn't create room: %s" % _room_name) - # TODO: Show feedback in the popup + # Setup the prop helper and use it to create the prop + _factory = PopochiuRoomFactory.new(_main_dock) + + if _factory.create( + _new_room_name, + _set_as_main_check.button_pressed + ) != ResultCodes.SUCCESS: + # TODO: show a message in the popup! return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Add the created room to Popochiu's rooms list - if _main_dock.add_resource_to_popochiu( - 'rooms', ResourceLoader.load(_room_path + '.tres') - ) != OK: - push_error( - "[Popochiu] Couldn't add the created room to Popochiu: %s" %\ - _room_name - ) - # TODO: Show feedback in the popup - return - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Add the room to the R singleton - PopochiuResources.update_autoloads(true) - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Update the list of rooms in the dock - var row := (_main_dock as PopochiuDock).add_to_list( - Constants.Types.ROOM, _pascal_name - ) - - # Establecer como la escena principal - # Changed _set_as_main_check.pressed to _set_as_main_check.button_pressed - # in order to fix #56 - if _set_as_main_check.button_pressed: - _main_dock.set_main_scene(room_resource.scene) - row.is_main = true # So the Heart icon shows + + var room_scene = _factory.get_obj_scene() # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ # Open the scene in the editor await get_tree().create_timer(0.1).timeout - _main_dock.ei.select_file(_room_path + '.tscn') - _main_dock.ei.open_scene_from_path(_room_path + '.tscn') + _main_dock.ei.select_file(room_scene.scene_file_path) + _main_dock.ei.open_scene_from_path(room_scene.scene_file_path) # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ # That's all! @@ -177,8 +72,7 @@ func _create() -> void: func _clear_fields() -> void: - _room_name = '' - _room_path = '' + _new_room_name = '' _set_as_main_check.button_pressed = false @@ -188,27 +82,22 @@ func set_main_dock(node: Panel) -> void: if not _main_dock: return - # res://popochiu/rooms - _room_path_template = _main_dock.ROOMS_PATH + '%s/room_%s' - # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PRIVATE ░░░░ func _update_name(new_text: String) -> void: super(new_text) if _name: - _room_name = _name.to_snake_case() - _pascal_name = _name - _room_path = _room_path_template % [_room_name, _room_name] + _new_room_name = _name.to_snake_case() _info.text = ( 'In [b]%s[/b] the following files will be created:\ \n[code]%s, %s and %s[/code]' \ % [ - _main_dock.ROOMS_PATH + _room_name, - 'room_' + _room_name + '.tscn', - 'room_' + _room_name + '.gd', - 'room_' + _room_name + '.tres' + _main_dock.ROOMS_PATH + _new_room_name, + 'room_' + _new_room_name + '.tscn', + 'room_' + _new_room_name + '.gd', + 'room_' + _new_room_name + '.tres' ] ) _info.show() diff --git a/addons/popochiu/editor/popups/create_walkable_area/create_walkable_area.gd b/addons/popochiu/editor/popups/create_walkable_area/create_walkable_area.gd index cc78890b..0bc047a5 100644 --- a/addons/popochiu/editor/popups/create_walkable_area/create_walkable_area.gd +++ b/addons/popochiu/editor/popups/create_walkable_area/create_walkable_area.gd @@ -2,22 +2,12 @@ @tool extends 'res://addons/popochiu/editor/popups/creation_popup.gd' -const SCRIPT_TEMPLATE :=\ -'res://addons/popochiu/engine/templates/walkable_area_template.gd' -const WALKABLE_AREA_SCENE :=\ -'res://addons/popochiu/engine/objects/walkable_area/popochiu_walkable_area.tscn' -const Constants := preload('res://addons/popochiu/popochiu_resources.gd') -const TabRoom := preload("res://addons/popochiu/editor/main_dock/tab_room.gd") - +## TODO: remove this legacy... var room_tab: VBoxContainer = null var _room: Node2D = null var _new_walkable_area_name := '' -var _new_walkable_area_path := '' -var _walkable_area_path_template := '' -var _room_path := '' -var _room_dir := '' -var _pascal_name := '' +var _factory: PopochiuWalkableAreaFactory # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ GODOT ░░░░ @@ -28,82 +18,24 @@ func _ready() -> void: # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ VIRTUAL ░░░░ func _create() -> void: - if _new_walkable_area_name.is_empty(): - _error_feedback.show() - return - - # TODO: Check if another WalkableArea was created in the same PATH. - # TODO: Remove created files if the creation process failed. - var script_path := _new_walkable_area_path + '.gd' - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the folder for the WalkableArea - assert( - DirAccess.make_dir_recursive_absolute( - _new_walkable_area_path.get_base_dir() - ) == OK, - '[Popochiu] Could not create walkable_area folder for '\ - + _new_walkable_area_name - ) - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the script for the WalkableArea - var walkable_area_template := load(SCRIPT_TEMPLATE) - if ResourceSaver.save(walkable_area_template, script_path) != OK: - push_error( - "[Popochiu] Couldn't create script: %s.gd" % _new_walkable_area_name - ) - # TODO: Show feedback in the popup + # Setup the region helper and use it to create the region + _factory = PopochiuWalkableAreaFactory.new(_main_dock) + + if _factory.create(_new_walkable_area_name, _room) != ResultCodes.SUCCESS: + # TODO: show a message in the popup! return + var walkable_area = _factory.get_obj_scene() + # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the new WalkableArea and add it to the room - var walkable_area: PopochiuWalkableArea = ResourceLoader.load( - WALKABLE_AREA_SCENE - ).instantiate() - walkable_area.set_script(ResourceLoader.load(script_path)) - walkable_area.name = _pascal_name - walkable_area.script_name = _pascal_name - walkable_area.description = _new_walkable_area_name.capitalize() - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Create the NavigationRegion2D (instead of using the one that - # was part of the original scene) - var perimeter := NavigationRegion2D.new() - walkable_area.add_child(perimeter) - perimeter.name = 'Perimeter' - - var polygon := NavigationPolygon.new() - polygon.agent_radius = 0 - polygon.add_outline(PackedVector2Array([ - Vector2(-10, -10), Vector2(10, -10), Vector2(10, 10), Vector2(-10, 10) - ])) - polygon.make_polygons_from_outlines() - - perimeter.navpoly = polygon - perimeter.modulate = Color.GREEN - - # Attach the walkable area to the room - _room.get_node('WalkableAreas').add_child(walkable_area) - - # Make the room the owner of both the Node2D and its NavigationRegion2D - walkable_area.owner = _room - perimeter.owner = _room - - walkable_area.position = Vector2( - ProjectSettings.get_setting(PopochiuResources.DISPLAY_WIDTH), - ProjectSettings.get_setting(PopochiuResources.DISPLAY_HEIGHT) - ) / 2.0 - - _main_dock.ei.save_scene() - - # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Abrir las propiedades de la walkable area creada en el Inspector + # Open the properties of the created region in the inspector + # Done here because the creation is interactive in this case await get_tree().create_timer(0.1).timeout PopochiuEditorHelper.select_node(walkable_area) # ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ - # Fin + # End hide() @@ -111,16 +43,11 @@ func _clear_fields() -> void: super() _new_walkable_area_name = '' - _new_walkable_area_path = '' # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PUBLIC ░░░░ func room_opened(r: Node2D) -> void: _room = r - _room_path = _room.scene_file_path - _room_dir = _room_path.get_base_dir() - _walkable_area_path_template = _room_dir +\ - '/walkable_areas/%s/walkable_area_%s' # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ PRIVATE ░░░░ @@ -129,14 +56,11 @@ func _update_name(new_text: String) -> void: if _name: _new_walkable_area_name = _name.to_snake_case() - _pascal_name = _name - _new_walkable_area_path = _walkable_area_path_template %\ - [_new_walkable_area_name, _new_walkable_area_name] _info.text = ( - 'In [b]%s[/b] the following files will be created:\n[code]%s[/code]'\ + 'In [b]%s[/b] the following files will be created: [code]%s[/code]'\ % [ - _room_dir + '/walkable_areas', + _room.scene_file_path.get_base_dir() + '/walkable_areas', 'walkable_area_' + _new_walkable_area_name + '.gd' ] ) diff --git a/addons/popochiu/editor/popups/creation_popup.gd b/addons/popochiu/editor/popups/creation_popup.gd index a9975d48..71a0f435 100644 --- a/addons/popochiu/editor/popups/creation_popup.gd +++ b/addons/popochiu/editor/popups/creation_popup.gd @@ -56,11 +56,4 @@ func _update_name(new_text: String) -> void: if _error_feedback.visible: _error_feedback.hide() - var casted_name := PackedStringArray() - for idx in new_text.length(): - if idx == 0: - casted_name.append(new_text[idx].to_upper()) - else: - casted_name.append(new_text[idx]) - - _name = ''.join(casted_name).strip_edges() + _name = new_text.to_pascal_case() diff --git a/addons/popochiu/engine/objects/character/popochiu_character.gd b/addons/popochiu/engine/objects/character/popochiu_character.gd index 049f93e4..72669a04 100644 --- a/addons/popochiu/engine/objects/character/popochiu_character.gd +++ b/addons/popochiu/engine/objects/character/popochiu_character.gd @@ -22,6 +22,7 @@ signal move_ended @export var can_move := true @export var ignore_walkable_areas := false @export var anti_glide_animation: bool = false +@export_category("Aseprite") var position_stored = null var last_room := '' @@ -54,20 +55,12 @@ func _ready(): child.frame_changed.connect(_update_position) func _get_property_list(): - var properties = [] - properties.append({ - name = "Aseprite", - type = TYPE_NIL, - usage = PROPERTY_USAGE_CATEGORY - }) - # This is needed or the category won't be shown in the - # inspector. AsepriteImporterInspectorPlugin hides it. - properties.append({ - name = "popochiu_placeholder", - type = TYPE_NIL, - }) - - return properties + return [ + { + name = "popochiu_placeholder", + type = TYPE_NIL, + } + ] # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ VIRTUAL ░░░░ diff --git a/addons/popochiu/engine/objects/clickable/popochiu_clickable.gd b/addons/popochiu/engine/objects/clickable/popochiu_clickable.gd index 6f961984..048fb963 100644 --- a/addons/popochiu/engine/objects/clickable/popochiu_clickable.gd +++ b/addons/popochiu/engine/objects/clickable/popochiu_clickable.gd @@ -13,10 +13,12 @@ const CURSOR := preload('res://addons/popochiu/engine/cursor/cursor.gd') @export var walk_to_point := Vector2.ZERO : set = set_walk_to_point @export var cursor: CURSOR.Type = CURSOR.Type.NONE @export var always_on_top := false +@export var interaction_polygon := PackedVector2Array() var room: Node2D = null : set = set_room # It is a PopochiuRoom var times_clicked := 0 var times_right_clicked := 0 +var editing_polygon := false @onready var _description_code := description @@ -27,10 +29,21 @@ func _ready(): if Engine.is_editor_hint(): hide_helpers() + + if get_node_or_null("InteractionPolygon") == null: return + + if interaction_polygon.is_empty(): + interaction_polygon = get_node('InteractionPolygon').polygon + else: + get_node('InteractionPolygon').polygon = interaction_polygon + return else: $BaselineHelper.free() $WalkToHelper.free() + + if get_node_or_null("InteractionPolygon"): + get_node("InteractionPolygon").polygon = interaction_polygon visibility_changed.connect(_toggle_input) @@ -78,12 +91,15 @@ func _process(delta): if Engine.is_editor_hint(): if walk_to_point != get_node('WalkToHelper').position: walk_to_point = get_node('WalkToHelper').position - + notify_property_list_changed() elif baseline != get_node('BaselineHelper').position.y: baseline = get_node('BaselineHelper').position.y - + notify_property_list_changed() + + if editing_polygon: + interaction_polygon = get_node('InteractionPolygon').polygon # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ VIRTUAL ░░░░ @@ -111,11 +127,17 @@ func _on_item_used(item: PopochiuInventoryItem) -> void: func hide_helpers() -> void: $BaselineHelper.hide() $WalkToHelper.hide() + + if get_node_or_null("InteractionPolygon"): + $InteractionPolygon.hide() func show_helpers() -> void: $BaselineHelper.show() $WalkToHelper.show() + + if get_node_or_null("InteractionPolygon"): + $InteractionPolygon.show() func queue_disable() -> Callable: @@ -199,6 +221,10 @@ func _translate() -> void: ) +func _on_interaction_polygon_changed() -> void: + prints("Me la cambiaron") + + # ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ SET & GET ░░░░ func set_baseline(value: int) -> void: baseline = value diff --git a/addons/popochiu/engine/objects/graphic_interface/history/components/dialog_line.tscn b/addons/popochiu/engine/objects/graphic_interface/history/components/dialog_line.tscn index a62dff61..1ecce2e9 100644 --- a/addons/popochiu/engine/objects/graphic_interface/history/components/dialog_line.tscn +++ b/addons/popochiu/engine/objects/graphic_interface/history/components/dialog_line.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=2 format=3 uid="uid://b6dm3xvvr7dvi"] -[ext_resource type="FontFile" uid="uid://c7ig50my7y048" path="res://addons/popochiu/fonts/monkeyisland_1991.ttf" id="1_o6vvl"] +[ext_resource type="FontFile" uid="uid://dixh1egf7k2fb" path="res://addons/popochiu/fonts/monkeyisland_1991.ttf" id="1_o6vvl"] [node name="DialogLine" type="Label"] anchors_preset = 15 diff --git a/addons/popochiu/engine/objects/graphic_interface/history/components/interaction_line.tscn b/addons/popochiu/engine/objects/graphic_interface/history/components/interaction_line.tscn index 7bd54582..fd26607a 100644 --- a/addons/popochiu/engine/objects/graphic_interface/history/components/interaction_line.tscn +++ b/addons/popochiu/engine/objects/graphic_interface/history/components/interaction_line.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=2 format=3 uid="uid://2mnjw3qsi8hc"] -[ext_resource type="FontFile" uid="uid://c7ig50my7y048" path="res://addons/popochiu/fonts/monkeyisland_1991.ttf" id="1_i1qxr"] +[ext_resource type="FontFile" uid="uid://dixh1egf7k2fb" path="res://addons/popochiu/fonts/monkeyisland_1991.ttf" id="1_i1qxr"] [node name="InteractionLine" type="Label"] anchors_preset = 15 diff --git a/addons/popochiu/engine/objects/hotspot/popochiu_hotspot.tscn b/addons/popochiu/engine/objects/hotspot/popochiu_hotspot.tscn index d1b731eb..6dee2332 100644 --- a/addons/popochiu/engine/objects/hotspot/popochiu_hotspot.tscn +++ b/addons/popochiu/engine/objects/hotspot/popochiu_hotspot.tscn @@ -4,7 +4,6 @@ [node name="Hotspot" type="Area2D"] script = ExtResource("1") -walk_to_point = Vector2(0, 0) cursor = 1 [node name="BaselineHelper" type="Line2D" parent="."] @@ -19,3 +18,7 @@ offset_top = -2.5 offset_right = 2.5 offset_bottom = 2.5 color = Color(0, 1, 1, 1) + +[node name="InteractionPolygon" type="CollisionPolygon2D" parent="."] +modulate = Color(0, 0, 1, 1) +polygon = PackedVector2Array(-12, -12, 12, -12, 12, 12, -12, 12) diff --git a/addons/popochiu/engine/objects/prop/popochiu_prop.tscn b/addons/popochiu/engine/objects/prop/popochiu_prop.tscn index d76cec51..a4b467ad 100644 --- a/addons/popochiu/engine/objects/prop/popochiu_prop.tscn +++ b/addons/popochiu/engine/objects/prop/popochiu_prop.tscn @@ -18,4 +18,10 @@ offset_right = 2.5 offset_bottom = 2.5 color = Color(0, 1, 1, 1) +[node name="InteractionPolygon" type="CollisionPolygon2D" parent="."] +polygon = PackedVector2Array(-12, -12, 12, -12, 12, 12, -12, 12) + [node name="Sprite2D" type="Sprite2D" parent="."] +texture_filter = 1 + +[node name="AnimationPlayer" type="AnimationPlayer" parent="."] diff --git a/addons/popochiu/engine/objects/room/popochiu_room.gd b/addons/popochiu/engine/objects/room/popochiu_room.gd index 5ae5ff58..9e968d22 100644 --- a/addons/popochiu/engine/objects/room/popochiu_room.gd +++ b/addons/popochiu/engine/objects/room/popochiu_room.gd @@ -17,6 +17,7 @@ extends Node2D @export var limit_right := INF @export var limit_top := INF @export var limit_bottom := INF +@export_category("Aseprite") var is_current := false : set = set_is_current @@ -59,6 +60,15 @@ func _ready(): E.room_readied(self) +func _get_property_list() -> Array[Dictionary]: + return [ + { + name = "popochiu_placeholder", + type = TYPE_NIL, + } + ] + + func _physics_process(delta): if _moving_characters.is_empty(): return diff --git a/addons/popochiu/engine/popochiu.gd b/addons/popochiu/engine/popochiu.gd index 2e710e0e..9dd3f255 100644 --- a/addons/popochiu/engine/popochiu.gd +++ b/addons/popochiu/engine/popochiu.gd @@ -159,7 +159,7 @@ func _input(event: InputEvent) -> void: if event.is_action_released('popochiu-skip'): cutscene_skipped = true $TransitionLayer.play_transition( - TransitionLayer.PASS_DOWN_IN, + $TransitionLayer.PASS_DOWN_IN, settings.skip_cutscene_time ) diff --git a/addons/popochiu/engine/templates/empty_script_template.gd b/addons/popochiu/engine/templates/empty_script_template.gd new file mode 100644 index 00000000..530bfd45 --- /dev/null +++ b/addons/popochiu/engine/templates/empty_script_template.gd @@ -0,0 +1,8 @@ +# This script is empty and is necessary to programmatically +# create an empty script to hold the _template.gd +# source file, after the placeholders have been populated. +# +# It is necessary from Godot 4.1 or an error is raised when the +# template script is loaded. +# +# Do not erase this script. \ No newline at end of file diff --git a/addons/popochiu/icons/interaction_polygon.png b/addons/popochiu/icons/interaction_polygon.png new file mode 100644 index 0000000000000000000000000000000000000000..9e9f2eef04e15677b7d0ca5d52f59166a827e52a GIT binary patch literal 156 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|!aQ9ZLo9le zb4<4VIM2ZPHQnF9NPuy|*9{8ZGAA|v^skf=eq&oFb1qHEjoGn4W%&pDjoTKlUVejh zFm}wxAT(A)jbW9q;r>jnLKAM^b5 BINJaK literal 0 HcmV?d00001 diff --git a/addons/popochiu/icons/interaction_polygon.png.import b/addons/popochiu/icons/interaction_polygon.png.import new file mode 100644 index 00000000..68363e01 --- /dev/null +++ b/addons/popochiu/icons/interaction_polygon.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cesndtvbchw7v" +path="res://.godot/imported/interaction_polygon.png-28f3d05312276a3121232981bf4f2bee.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://addons/popochiu/icons/interaction_polygon.png" +dest_files=["res://.godot/imported/interaction_polygon.png-28f3d05312276a3121232981bf4f2bee.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/addons/popochiu/popochiu_export_plugin.gd b/addons/popochiu/popochiu_export_plugin.gd index b02d5d71..7ebae6be 100644 --- a/addons/popochiu/popochiu_export_plugin.gd +++ b/addons/popochiu/popochiu_export_plugin.gd @@ -9,11 +9,11 @@ func _export_begin(features: PackedStringArray, is_debug: bool, path: String, fl file.close() - -## Logic for Aseprite Importer -## This code removes importer's metadata from nodes before exporting them -## Thanks to Vinicius Gerevini and his Aseprite Wizard plugin for that! -## TODO: may be moved to another file so we keep things separated +# Logic for Aseprite Importer +# This code removes importer's metadata from nodes before exporting them +# Thanks to Vinicius Gerevini and his Aseprite Wizard plugin for that! +# This code is run independent of the importer to be enabled, to clean things up. +# TODO: may be moved to another file so we keep things separated func _export_file(path: String, type: String, features: PackedStringArray) -> void: if type != "PackedScene": return diff --git a/addons/popochiu/popochiu_plugin.gd b/addons/popochiu/popochiu_plugin.gd index 669ab8a4..e69267df 100644 --- a/addons/popochiu/popochiu_plugin.gd +++ b/addons/popochiu/popochiu_plugin.gd @@ -27,6 +27,7 @@ var _inspector_plugins := [] var _selected_node: Node = null var _btn_baseline := Button.new() var _btn_walk_to := Button.new() +var _btn_interaction_polygon := Button.new() var _types_helper: Resource = null var _tool_btn_stylebox :=\ _editor_interface.get_base_control().get_theme_stylebox("normal", "Button") @@ -70,7 +71,17 @@ func _enter_tree() -> void: config.ei = _editor_interface config.initialize_editor_settings() config.initialize_project_settings() - + + # Configure main dock to be passed down the plugin chain + # TODO: Get rid of this cascading assignment and switch to + # a signalbus instead! + main_dock = load(PopochiuResources.MAIN_DOCK_PATH).instantiate() + main_dock.ei = _editor_interface + main_dock.fs = _editor_file_system + main_dock.focus_mode = Control.FOCUS_ALL + PopochiuEditorHelper.ei = _editor_interface + PopochiuEditorHelper.undo_redo = get_undo_redo() + for path in [ 'res://addons/popochiu/editor/inspector/character_inspector_plugin.gd', 'res://addons/popochiu/editor/inspector/walkable_area_inspector_plugin.gd', @@ -82,19 +93,13 @@ func _enter_tree() -> void: eip.set('ei', _editor_interface) eip.set('fs', _editor_file_system) eip.set('config', config) + eip.set('main_dock', main_dock) # TODO: change with SignalBus _inspector_plugins.append(eip) add_inspector_plugin(eip) _export_plugin = preload('popochiu_export_plugin.gd').new() add_export_plugin(_export_plugin) - - main_dock = load(PopochiuResources.MAIN_DOCK_PATH).instantiate() - main_dock.ei = _editor_interface - main_dock.fs = _editor_file_system - main_dock.focus_mode = Control.FOCUS_ALL - PopochiuEditorHelper.ei = _editor_interface - PopochiuEditorHelper.undo_redo = get_undo_redo() add_control_to_dock(DOCK_SLOT_RIGHT_BL, main_dock) @@ -360,10 +365,15 @@ func _check_nodes() -> void: or _types_helper.is_hotspot(_selected_node): _btn_baseline.set_pressed_no_signal(false) _btn_walk_to.set_pressed_no_signal(false) + _btn_interaction_polygon.set_pressed_no_signal(false) - _btn_baseline.get_parent().show() + _btn_baseline.show() + _btn_walk_to.show() + _btn_interaction_polygon.show() else: - _btn_baseline.get_parent().hide() + _btn_baseline.hide() + _btn_walk_to.hide() + _btn_interaction_polygon.hide() func _on_files_moved(old_file: String, new_file: String) -> void: @@ -393,21 +403,29 @@ func _create_container_buttons() -> void: _btn_walk_to.add_theme_stylebox_override('hover', _tool_btn_stylebox) _btn_walk_to.pressed.connect(_select_walk_to) + _btn_interaction_polygon.icon = preload('res://addons/popochiu/icons/interaction_polygon.png') + _btn_interaction_polygon.tooltip_text = 'Interaction Polygon' + _btn_interaction_polygon.toggle_mode = true + _btn_interaction_polygon.add_theme_stylebox_override('normal', _tool_btn_stylebox) + _btn_interaction_polygon.add_theme_stylebox_override('hover', _tool_btn_stylebox) + _btn_interaction_polygon.pressed.connect(_select_interaction_polygon) + hbox.add_child(_btn_baseline) hbox.add_child(_btn_walk_to) + hbox.add_child(_btn_interaction_polygon) add_control_to_container( EditorPlugin.CONTAINER_CANVAS_EDITOR_MENU, hbox ) - - hbox.hide() + + _btn_baseline.hide() + _btn_walk_to.hide() func _select_walk_to() -> void: _btn_walk_to.set_pressed_no_signal(true) _btn_baseline.set_pressed_no_signal(false) - _editor_interface.get_selection().clear() if _types_helper.is_prop(_selected_node)\ @@ -422,9 +440,7 @@ func _select_walk_to() -> void: func _select_baseline() -> void: - _btn_baseline.set_pressed_no_signal(true) _btn_walk_to.set_pressed_no_signal(false) - _editor_interface.get_selection().clear() if _types_helper.is_prop(_selected_node)\ @@ -438,6 +454,23 @@ func _select_baseline() -> void: ) +func _select_interaction_polygon() -> void: + _btn_walk_to.set_pressed_no_signal(false) + _btn_baseline.set_pressed_no_signal(false) + _btn_interaction_polygon.set_pressed_no_signal(true) + + var collision_polygon: CollisionPolygon2D = null + + if _types_helper.is_prop(_selected_node)\ + or _types_helper.is_hotspot(_selected_node): + collision_polygon = _selected_node.get_node('InteractionPolygon') + else: + collision_polygon = _selected_node.get_node('../InteractionPolygon') + + _editor_interface.edit_node(collision_polygon) + collision_polygon.get_parent().editing_polygon = true + + func _move_to_project(id: int) -> void: # Move files and folders so developer can overwrite them if id == PopochiuResources.GI: diff --git a/addons/popochiu/popochiu_resources.gd b/addons/popochiu/popochiu_resources.gd index 5c037545..cc20fb16 100644 --- a/addons/popochiu/popochiu_resources.gd +++ b/addons/popochiu/popochiu_resources.gd @@ -144,7 +144,7 @@ const SNGL_SETUP := { I_SNGL : { interface = IINVENTORY, section = 'inventory_items', - 'class' = 'res://popochiu/inventory_items/%s/item_%s.gd', + 'class' = 'res://popochiu/inventory_items/%s/inventory_item_%s.gd', 'const' = "const PII%s := preload('%s')\n", node = "var %s: PII%s : get = get_%s\n", 'func' = "func get_%s() -> PII%s: return super.get_item_instance('%s')\n", diff --git a/popochiu/rooms/house/room_house.tscn b/popochiu/rooms/house/room_house.tscn new file mode 100644 index 00000000..48edbc56 --- /dev/null +++ b/popochiu/rooms/house/room_house.tscn @@ -0,0 +1,131 @@ +[gd_scene load_steps=12 format=3 uid="uid://imgyvjiqcabs"] + +[ext_resource type="Script" path="res://popochiu/rooms/house/room_house.gd" id="1_2rfwu"] +[ext_resource type="PackedScene" uid="uid://jrp1r8uco7vy" path="res://popochiu/rooms/house/props/background/prop_background.tscn" id="2_amufg"] +[ext_resource type="Script" path="res://popochiu/rooms/house/props/background/prop_background.gd" id="3_fxlxc"] +[ext_resource type="PackedScene" uid="uid://dbkathyetrso7" path="res://popochiu/rooms/house/props/door/prop_door.tscn" id="4_qpaur"] +[ext_resource type="Script" path="res://popochiu/rooms/house/props/door/prop_door.gd" id="5_h0lr0"] +[ext_resource type="PackedScene" uid="uid://mqwbrgbk0llc" path="res://popochiu/rooms/house/props/drawer/prop_drawer.tscn" id="6_uvcj4"] +[ext_resource type="Script" path="res://popochiu/rooms/house/props/drawer/prop_drawer.gd" id="7_o4euj"] +[ext_resource type="PackedScene" uid="uid://diukdigj0f4io" path="res://popochiu/rooms/house/props/table/prop_table.tscn" id="8_hg5ln"] +[ext_resource type="Script" path="res://popochiu/rooms/house/props/table/prop_table.gd" id="9_ktt1q"] +[ext_resource type="PackedScene" uid="uid://1467eig2vwbo" path="res://popochiu/rooms/house/props/toy_car/prop_toy_car.tscn" id="10_nkfsd"] +[ext_resource type="Script" path="res://popochiu/rooms/house/props/toy_car/prop_toy_car.gd" id="11_earee"] + +[node name="RoomHouse" type="Node2D"] +script = ExtResource("1_2rfwu") +script_name = "House" +popochiu_placeholder = null +metadata/_popochiu_aseprite_config_ = { +"o_folder": "", +"o_name": "", +"only_visible_layers": false, +"op_exp": true, +"source": "res://popochiu/rooms/house/house.aseprite", +"tags": [{ +"direction": "forward", +"from": 0, +"import": true, +"loops": true, +"prop_clickable": true, +"prop_visible": true, +"tag_name": "background", +"to": 1.0 +}, { +"direction": "forward", +"from": 0, +"import": true, +"loops": true, +"prop_clickable": true, +"prop_visible": true, +"tag_name": "door", +"to": 2.0 +}, { +"direction": "forward", +"from": 0, +"import": true, +"loops": true, +"prop_clickable": true, +"prop_visible": true, +"tag_name": "drawer", +"to": 2.0 +}, { +"direction": "forward", +"from": 0, +"import": true, +"loops": true, +"prop_clickable": true, +"prop_visible": true, +"tag_name": "table", +"to": 1.0 +}, { +"direction": "forward", +"from": 0, +"import": true, +"loops": true, +"prop_clickable": true, +"prop_visible": true, +"tag_name": "toy_car", +"to": 1.0 +}], +"wipe_old_anims": true +} + +[node name="WalkableAreas" type="Node2D" parent="."] + +[node name="Props" type="Node2D" parent="."] + +[node name="Background" type="Area2D" parent="Props" instance=ExtResource("2_amufg")] +z_index = -1 +position = Vector2(160, 90) +script = ExtResource("3_fxlxc") +script_name = "Background" +description = "Background" +baseline = -90 +cursor = 1 +interaction_polygon = PackedVector2Array(-12, -12, 12, -12, 12, 12, -12, 12) +metadata/ANIM_NAME = "background" + +[node name="Door" type="Area2D" parent="Props" instance=ExtResource("4_qpaur")] +position = Vector2(160, 90) +script = ExtResource("5_h0lr0") +script_name = "Door" +description = "Door" +cursor = 1 +interaction_polygon = PackedVector2Array(-12, -12, 12, -12, 12, 12, -12, 12) +metadata/ANIM_NAME = "door" + +[node name="Drawer" type="Area2D" parent="Props" instance=ExtResource("6_uvcj4")] +position = Vector2(160, 90) +script = ExtResource("7_o4euj") +script_name = "Drawer" +description = "Drawer" +cursor = 1 +interaction_polygon = PackedVector2Array(-12, -12, 12, -12, 12, 12, -12, 12) +metadata/ANIM_NAME = "drawer" + +[node name="Table" type="Area2D" parent="Props" instance=ExtResource("8_hg5ln")] +position = Vector2(160, 90) +script = ExtResource("9_ktt1q") +script_name = "Table" +description = "Table" +cursor = 1 +interaction_polygon = PackedVector2Array(-12, -12, 12, -12, 12, 12, -12, 12) +metadata/ANIM_NAME = "table" + +[node name="ToyCar" type="Area2D" parent="Props" instance=ExtResource("10_nkfsd")] +position = Vector2(160, 90) +script = ExtResource("11_earee") +script_name = "ToyCar" +description = "Toy Car" +cursor = 1 +interaction_polygon = PackedVector2Array(-12, -12, 12, -12, 12, 12, -12, 12) +metadata/ANIM_NAME = "toy_car" + +[node name="Hotspots" type="Node2D" parent="."] + +[node name="Regions" type="Node2D" parent="."] + +[node name="Markers" type="Node2D" parent="."] + +[node name="Characters" type="Node2D" parent="."] diff --git a/popochiu/sources/aseprite/room_exterior.aseprite b/popochiu/sources/aseprite/room_exterior.aseprite new file mode 100644 index 0000000000000000000000000000000000000000..8024404e40e6eb04ce981fe3bc0fbaf58130e280 GIT binary patch literal 9508 zcmeI2c{J4f|HntRWDRA>;0oQsSSF$vDp3hZc7_O9#x}`LDqLmBPJ`)MlPqH=Mhhm( z7)y+OjVZ2mY|-$0yZ4@Zy7zp)fBe4Z{C?+m&aaQ-aXz2-`!n9J=kxvfoX`9D8R8NU z$V4s+Bp-4IA_}2{Kp>9~0Q|MVAawhU5H9fZKe0s|hCsGgczz$12Mll&{4$IFKIC`9 z!9at6DD2M`neTca5I#}mJqSeNED}%^`2>Og6rE>P5CP!qo><6kpJ_xrW>*C7OQ2y)?; zor|NFySLlz1CYO6gAfQKEHG33tI)67)8M! z2HW0W2JYY3zUd1?qr_f0{`=h-rgV{ET&!}e+!mz_DZiL$TzJVVM!3g)V(HkXrBIL& zrYeSZn)Y?|D}khrW!~D@;bT+G(m{DKqT-Waeuto|40Q|;h-+0tGOam$hrTpugl)CENKh~`6Tr+vK0+rE>u^^zmmHivC2}AJR9+VHsB$;fq zHZ-C_vBrmdBhBO6+l4UHxZ%z@SIpTNXEhZ5taN38!QxHl@{dkm1`e4F&6Sv}ZPixC z^{iK-v{aYoSGy6_2+n2Id2d%5%Ib{&wcI7GfOAC+ogcFMkqfUcFVsneYzhqKk?8$| zz7+bXS%3ql0;1XibTH*}C8jUaZW^k!G;Ca$y(#Yx#k#3Q9FGk>PTp#(TvRD`rtZSv z6)8?dn;qItm~}h3JW@8HJiB@+NcVPX_KijhvL|tb+@DjW7qCc8R7#=ps)hR)IiqPl zqgUn|UiFjvY34H8Bu;8!rCFWTG0k2{{asn!3S*yDrp+5D4tF22MV-!+9GU7tL1t2UetW-srMK|ZXG&29>ty{e zlw*CDD>8#3GhI3>YwH)i{gT78DbLVBog;Z#lnTqp@pkbSG$FO?Qi;qc?B5lvf4Kd+twZ=5Ml15c8sZaNjeE z{_(K*{)*S%zv9!a+=MRihB@*uL_H9rZxLTnmfT{0#1*-E|Mp6_Da68t-j%8UU0#$+ zk0KtAZ|t)gg)gFW;t6n?;mr2-NI+pg;Kt6(!l2d8@}>nYgB@!zY^pot#A46mLgMY=JLejWu%wUW*mQ^?%9xV z-L2WR$NqVzea1#$t@*Rjf>?88YwS-Md1RScctWy%^04|YZCYBTIuo9m1P!cw+biSjvA-764117BSKip zCK7u<1zz84(AurDGnKzE+m1m!Yrp*4ldW5cq;SD6Z`bn{QYPW=ie@+KwI1AMcU_#) zmrT|{pQ&10Q9bopTMTQS-q#}0{E#i>SxJv&#TH#oyDgj>+fqs{3t`173SUQj-7>Fz zvSIWxwzT-Ri!{b?SwIbWQLoxD3-3OYmL}9Y_qw02ZH6jRI{T#{O!o^qI=2p$tVnw3 ztDakz=4P1MQK~juw_KxHEWl$Af0rNEt7g+R)NecD-1mBGCC58s+k90Ik%=2In||67 zvORxP(O<(%NeyxG+~MSGMLyDI4%I7s{@x8}(`G&{*p*r%t;jW1E88ha>3!)UvzPU% z!mFStGys?3Mi|V&Wq6&$rA;bspULv(+N!X}rJ4CmT54`}2tA<$_2W*%ay@S{xg#4# zE8DVLTwFLr*BM=hUXn|i^4E~bB-h|soLjT^g5_AH(cml9tBpG8{$yxLzWCB3*DJ19 z1GSVV#_XGGE(^I%8a~b*^A^P^D={--rgL!xD}pOgAMPEUK8(nY(o`F-3(lf+)i7XC zJyUhbg=W;FX@un3d}@XYJ}K5TsJkl8`O?Ya%h$?+uCp>WlP8JWlTfga};CJw2M+zdvYF`|AUp^){nb1hWMt|HWbWPT> ztG}mvmC(J-3d`@iVk?6W(|5;P1ob|_1dgvJcwL$HKkdFe#axq)6vW2WUj1Y_nJUnL zkmkstcBL+^69YtoRj>+y4zx}x={Lp0lNxno!>W=%XUgob4n-?irLo1=QR?+!gxXQ@ zmHPN4Y^%xxWMd=#*DpycohKW-#otevzN)`*E{~EXjJa+NRlv6SHF+-A5M8{2Rd+`$ zbM|b|!Me>3tc179yP`3t?0h|h=CVhmq!JQ!Vu^RupF1h$`rLEt=idu zew-|nV4@Lpm%;TJa^@>Mdc8s-?U5twIsK+yN<_y>j-y6>CA~-OD~?1G7iSN?r=e|( zc7OY1dwycMF3~}AP59|Y-_ZnnZcE|ttiYaUS>HZ&cm*F*=73LnUU9+l4U_Lixjp32 zaSgr+Yp6@vHha*1n(r`4$b4lWCPd{9%qhy*)tD)lKj7YWJQ|T^65780*o!(h=S6LA z_Bqy>H)HjB=b^~pZ``uJ=*K6icN0nfR`S$P@340#%2xSa6XJS$ccEkR? zEtOINn@E%Dc{7_`gETv`N-UV`V6Vq8bQrFrijB@ep+%#`;6_zCOqXie zK;m51J15yo0}R-CgLq=GVq8VI_{U45ot)5^tBYHc-=aU{m1TacR395UH((j-9K;h% zvEg~96GrrLw7_YVl(0%sNQf4%QWj$F!@!Qze2q`hpkbCr35Z!P+r@h ztxku)OVW?jl_>U6QLjkP z2}opjy*JI{T5M>KaFRYV7j+P0wG~xnwkTjZz4J!92(#lj)VCKmQS=khtXg|edTDa! z4M8eW8>JS$i)`pCjP)-oDDvzfZy}afPQ9IZXzY^vYxfe%C^9H`&mX2#guh5QDV5o~ zS$i}>4vPq4fc<>p#s$ZtKH|A8RSp}NGP6L~E{AAk6RpvwmRz;D_p zETup9!TDKBpJkuWL%ck`5;9x2D77?Q$5%7`-0Y!~akWBSI+xG+v5A(D z(MxOFnS3lPt)X7Odq)pJLJ;41cNoGCyvt3H*P%bp6-J3LHBGy$%)))Xlw0}%_rdrJ zuO39BN_*Nq4w;%h&In5iJ6J`ByOJ$7z>*cVXS>e68PhtlFcU=|YwzQT4?6Su#0}x` zY&u@KPQg9Q=i}1*>|(dAV%sPvY3UhfJ2ngj-4KyAA{L(Dcr6fH$k!-hRvpyY2Y21& zY;$AlUkB?;Mo|!QAjaQ0t^nZ%jyLz8<9H~?#x!C1MC$Hb3yXg!$AWPreHTs*_JZCB zLMRzG7V~@eC{S5B(Mz~@^J24D6ReTIzeEZdK^ppXxD)G)@;JinS-ZMk88{H<)?I{d z;%9cV<=ZE%0^Hj@YQqI`yw9v&fEr(%uOl43^{Y_-=0fX}Dk11)QkiMZ*ij=+@%uE! zKFRjU5o@<6A4|%lhwARAscWOotM%hw`R27nOJ|XHI zM0e=(ICP_)TbB+#%-h^cC}No?mF$O#R_goYydnD5BMu1bH$5UWvluM*%)xV5nxQ5X86f(cHa!+D!S8o`pQxTYLf+QGcYEKd92#Qq+7ohBOaHqcZsB8@I- zr}21G;?r}x?(Cj6%EpJ^c;t>l3k5{I$rSMkMLWkt8CBJ8+5tG_f(e&u#>6tKrJ6LG z&E%nkh%~?AKFrVXr{C`168>2v-i7JllAeeC{V?_9n&y|BL{&DL-1U-zC%2e{qq4}F zTF|Hl-qRYVkqvTRPkTd|nC+oV)~Mq(!B2(Q=$k!l?|Q?W{rUYp(?VZySjp5KD=lbt zU=q|iL*E>7XIr3a#`Bls5%BQfHDAarNOXkr5#xVMiq>C~qW-r@;rnA!7-Tt)-d_&k z(uF<$$o3*wS&Q#DnLVeD6_b&;jVcJGdyjhzk!zXNw_UrqvPT?~WMl4?;m>r{z5kj8 zqp~@xk~IPSDbI!`$Hu#}cN;C;5w#af)}Bz9R*zS)X%JnPgBR=4@33?P*C;@+0(-LF z1GcGj@3dE~_=TmZp~GRMsB&*5kB~Pb95e8A_p-N6Oh0!#U108(qkV82gxWC~)8F7! z;L%b2cH(x6aR|?+Qpr}f+nzT%?sv;Y36ZxtH1v74*0M1Mz|USb>1~4%8?kJT7UUs8 zQ9i+fu!Jy*6=zswPi`x0^MzvF6d?y_m#KU_`z%hhv#bM+W- zH6vba|3z0gc+qw6v50uw^TV1lVoZnCMK5vT%F1uPx+Y=DqO=Ra8J6C7dLN~cX!Fz1 zK;-_bt_A^Wcx~+{``DY(>T^`YxijUOPCIpDIh#~Qf(vrT4b`h3Kw5c5;Ff8~pvfck zs=efEg8Qu#+ZQLF!#ehcC`MNPZ`mBe_yakrlRt;OP-H>-D22lRzT_}!`1Zgk&Z_(n7J$ZoJ?eIa%$dU+On;KK&nZ8?LjSTy{J&1 zgL)q_pP>to8t6ugY~yS}n#kJJ*E^YaWqBjqOr*G{$4sYv101y$Lq@O?^(GeW#Gb^a z3v8dBGb5NB-fxESn=ZKGwAt~+f#sljME9V8xZn<$jfMkx|C*ig{Ixi_7xNHh+uE@%0cbeI;RK ga!}{z1)+fuI&G1&O^koe(*8dMfnCV|%a>#S1