From c4a6c67eb8aa22c716d0f1ad4fc41a91ab30edac Mon Sep 17 00:00:00 2001 From: Cake Date: Tue, 27 Feb 2024 09:15:21 +0100 Subject: [PATCH 01/55] Add Layered Portrait. --- .../LayeredPortrait/layered_portrait.gd | 170 ++++++++++++++++++ .../LayeredPortrait/layered_portrait.tscn | 36 ++++ 2 files changed, 206 insertions(+) create mode 100644 addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd create mode 100644 addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.tscn diff --git a/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd b/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd new file mode 100644 index 000000000..7e35711a0 --- /dev/null +++ b/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd @@ -0,0 +1,170 @@ +@tool +## Layered portrait scene. +## +## The parent class has a character and portrait variable. +extends DialogicPortrait + + +const HIDE_COMMAND := "hide" +const SHOW_COMMAND := "show" +const SET_COMMAND := "set" + +const OPERATORS = [HIDE_COMMAND, SHOW_COMMAND, SET_COMMAND] +static var OPERATORS_EXPRESSION := "|".join(OPERATORS) +static var REGEX_STRING := "(" + OPERATORS_EXPRESSION + ") (\\S+)" +static var REGEX := RegEx.create_from_string(REGEX_STRING) + + +## Load anything related to the given character and portrait +func _update_portrait(passed_character: DialogicCharacter, passed_portrait: String) -> void: + apply_character_and_portrait(passed_character, passed_portrait) + + + +func _find_sprites_recursively(start_node: Node) -> Array[Sprite2D]: + var sprites: Array[Sprite2D] = [] + + # Iterate through the children of the current node + for child in start_node.get_children(): + + if child is Sprite2D and child.texture != null: + var sprite := child as Sprite2D + sprites.append(sprite) + continue + + + var child_sprites := _find_sprites_recursively(child) + # Extend the list of sprites with the sprites found in the child node + sprites.append_array(child_sprites) + + return sprites + + +func _ready() -> void: + pass + #for sprite: Sprite2D in self._find_sprites_recursively(self): + # Get the sprite's height + # var sprite_height := sprite.texture.get_height() + # Set the offset to half of the sprite's height + #sprite.offset.y = -sprite_height + + +## A _command that will apply an effect to the layered portrait. +class LayerCommand: + enum CommandType { + SHOW_LAYER, + HIDE_LAYER, + SET_LAYER, + } + + var _path: String + var _type: CommandType + + ## Executes the _command. + func _execute(root: Node) -> void: + var target_node := root.get_node(_path) + + if target_node == null: + print("Layered Portrait had no node matching the _path: ", _path) + return + + match _type: + CommandType.SHOW_LAYER: + target_node.show() + + CommandType.HIDE_LAYER: + target_node.hide() + + CommandType.SET_LAYER: + var target_parent := target_node.get_parent() + + for child in target_parent.get_children(): + child.hide() + + target_node.show() + + +## Turns the input into a single [class LayerCommand] object. +## Returns `null` if the input cannot be parsed into a [class LayerCommand]. +func _parse_layer_command(input: String) -> LayerCommand: + var command := LayerCommand.new() + + var regex_match: RegExMatch = REGEX.search(input) + + if regex_match == null: + print("Layered Portrait had an invalid command: ", input) + return null + + var _path: String = regex_match.get_string(2) + var operator: String = regex_match.get_string(1) + + match operator: + SET_COMMAND: + command._type = LayerCommand.CommandType.SET_LAYER + + SHOW_COMMAND: + command._type = LayerCommand.CommandType.SHOW_LAYER + + HIDE_COMMAND: + command._type = LayerCommand.CommandType.HIDE_LAYER + + SET_COMMAND: + command._type = LayerCommand.CommandType.SET_LAYER + + ## We clean escape symbols and trim the spaces. + command._path = _path.replace("\\", "").strip_edges() + + return command + + +## Parses [param input] into an array of [class LayerCommand] objects. +func _parse_input_to_layer_commands(input: String) -> Array[LayerCommand]: + var commands: Array[LayerCommand] = [] + var command_parts := input.split(",") + + for command_part: String in command_parts: + + if command_part.is_empty(): + continue + + var _command := _parse_layer_command(command_part.strip_edges()) + + if not _command == null: + commands.append(_command) + + return commands + + +## The extra data will be turned into layer commands and then be executed. +func _set_extra_data(data: String) -> void: + var commands := _parse_input_to_layer_commands(data) + + for _command: LayerCommand in commands: + _command._execute(self) + + +func _get_covered_rect() -> Rect2: + var lowest_x: float = 0 + var lowest_y: float = 0 + var biggest_width: float = 0 + var biggest_height: float = 0 + + + for sprite: Sprite2D in self._find_sprites_recursively(self): + + if sprite.texture != null: + var rect: Rect2 = sprite.get_rect() + + if rect.position.x > lowest_x: + lowest_x = rect.position.x + + if rect.position.y > lowest_y: + lowest_y = rect.position.y + + if rect.size.x > biggest_width: + biggest_width = rect.size.x + + if rect.size.y > biggest_height: + biggest_height = rect.size.y + + return Rect2(lowest_x, lowest_y, biggest_width, biggest_height) diff --git a/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.tscn b/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.tscn new file mode 100644 index 000000000..fb37208a6 --- /dev/null +++ b/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.tscn @@ -0,0 +1,36 @@ +[gd_scene load_steps=5 format=3 uid="uid://jac4eurttev1"] + +[ext_resource type="Script" path="res://addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd" id="1_7l060"] +[ext_resource type="Texture2D" uid="uid://djqit26f4be4f" path="res://addons/dialogic/Example Assets/portraits/Princess/princess_blank.png" id="2_yttk1"] +[ext_resource type="Texture2D" uid="uid://c5aku2g01k6c6" path="res://addons/dialogic/Example Assets/portraits/Princess/shock.png" id="3_mexya"] +[ext_resource type="Texture2D" uid="uid://dsid4ye0q74nl" path="res://addons/dialogic/Example Assets/portraits/Princess/smile.png" id="4_wvcco"] + +[node name="LayeredPortrait" type="Node2D"] +script = ExtResource("1_7l060") + +[node name="Layer1" type="Sprite2D" parent="."] +position = Vector2(0, -1547) +texture = ExtResource("2_yttk1") +centered = false + +[node name="Group1" type="Control" parent="."] +layout_mode = 3 +anchors_preset = 0 +offset_right = 40.0 +offset_bottom = 40.0 + +[node name="Layer1" type="Sprite2D" parent="Group1"] +position = Vector2(508, -1446) +texture = ExtResource("3_mexya") +centered = false + +[node name="Layer2" type="Sprite2D" parent="Group1"] +position = Vector2(508, -1446) +texture = ExtResource("4_wvcco") +centered = false + +[node name="Layer3" type="Sprite2D" parent="Group1"] +centered = false + +[node name="Layer2" type="Sprite2D" parent="."] +centered = false From b23f3ca96d5e6385c8ab6b6745282f964fce73b6 Mon Sep 17 00:00:00 2001 From: Cake Date: Tue, 27 Feb 2024 09:16:52 +0100 Subject: [PATCH 02/55] Update extra when using `update` Character event. --- addons/dialogic/Modules/Character/event_character.gd | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/addons/dialogic/Modules/Character/event_character.gd b/addons/dialogic/Modules/Character/event_character.gd index a9b068b45..62d633e85 100644 --- a/addons/dialogic/Modules/Character/event_character.gd +++ b/addons/dialogic/Modules/Character/event_character.gd @@ -119,6 +119,8 @@ func _execute() -> void: finish() return + dialogic.Portraits.change_character_extradata(character, extra_data) + if set_portrait: dialogic.Portraits.change_character_portrait(character, portrait, false) @@ -139,7 +141,7 @@ func _execute() -> void: if animation_name: var final_animation_length: float = animation_length - var final_animation_repitions: int = animation_repeats + var final_animation_repetitions: int = animation_repeats if dialogic.Inputs.auto_skip.enabled: var time_per_event: float = dialogic.Inputs.auto_skip.time_per_event @@ -150,7 +152,7 @@ func _execute() -> void: character, animation_name, final_animation_length, - final_animation_repitions + final_animation_repetitions, ) if animation_wait: @@ -252,6 +254,7 @@ func from_text(string:String) -> void: action = Actions.UPDATE if result.get_string('name').strip_edges(): + if action == Actions.LEAVE and result.get_string('name').strip_edges() == "--All--": character_identifier = '--All--' else: @@ -272,12 +275,14 @@ func from_text(string:String) -> void: animation_name = shortcode_params.get('animation', '') var animLength = shortcode_params.get('length', '0.5').to_float() + if typeof(animLength) == TYPE_FLOAT: animation_length = animLength else: animation_length = animLength.to_float() - animation_wait = DialogicUtil.str_to_bool(shortcode_params.get('wait', 'false')) + var wait_for_animation: String = shortcode_params.get('wait', 'false') + animation_wait = DialogicUtil.str_to_bool(wait_for_animation) #repeat is supported on Update, the other two should not be checking this if action == Actions.UPDATE: From 54986013773ddea28fdfe7a5008be5d2ede0ecf9 Mon Sep 17 00:00:00 2001 From: Cake Date: Tue, 27 Feb 2024 09:17:09 +0100 Subject: [PATCH 03/55] Silence warning. --- addons/dialogic/Modules/Character/dialogic_portrait.gd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/dialogic/Modules/Character/dialogic_portrait.gd b/addons/dialogic/Modules/Character/dialogic_portrait.gd index 438506d60..fdd7aba37 100644 --- a/addons/dialogic/Modules/Character/dialogic_portrait.gd +++ b/addons/dialogic/Modules/Character/dialogic_portrait.gd @@ -55,7 +55,7 @@ func _set_mirror(mirror:bool) -> void: ## Function to accept and use the extra data, if the custom portrait wants to accept it -func _set_extra_data(data: String) -> void: +func _set_extra_data(_data: String) -> void: pass #endregion From dd10cd1a7138c86147adc65889f3ff5ff6a2fd87 Mon Sep 17 00:00:00 2001 From: Cake Date: Tue, 27 Feb 2024 09:18:56 +0100 Subject: [PATCH 04/55] Emit real value file path on `value_changed`. --- .../Editor/Events/Fields/field_file.gd | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/addons/dialogic/Editor/Events/Fields/field_file.gd b/addons/dialogic/Editor/Events/Fields/field_file.gd index 15d3dd8b9..45aac910d 100644 --- a/addons/dialogic/Editor/Events/Fields/field_file.gd +++ b/addons/dialogic/Editor/Events/Fields/field_file.gd @@ -49,13 +49,15 @@ func _load_display_info(info:Dictionary) -> void: placeholder = info.get('placeholder', '') resource_icon = info.get('icon', null) await ready + if resource_icon == null and info.has('editor_icon'): resource_icon = callv('get_theme_icon', info.editor_icon) -func _set_value(value:Variant) -> void: +func _set_value(value: Variant) -> void: current_value = value - var text := value + var text: String = value + if file_mode != EditorFileDialog.FILE_MODE_OPEN_DIR: text = value.get_file() %Field.tooltip_text = value @@ -70,9 +72,11 @@ func _set_value(value:Variant) -> void: %Field.custom_minimum_size.x = 0 %Field.expand_to_text_length = true - %Field.text = text + if not %Field.text == text: + value_changed.emit(property_name, current_value) + %Field.text = text - %ClearButton.visible = !value.is_empty() and !hide_reset + %ClearButton.visible = not value.is_empty() and not hide_reset #endregion @@ -87,12 +91,12 @@ func _on_OpenButton_pressed() -> void: func _on_file_dialog_selected(path:String) -> void: _set_value(path) - emit_signal("value_changed", property_name, path) + value_changed.emit(property_name, path) func clear_path() -> void: _set_value("") - emit_signal("value_changed", property_name, "") + value_changed.emit(property_name) #endregion @@ -100,16 +104,22 @@ func clear_path() -> void: #region DRAG AND DROP ################################################################################ -func _can_drop_data_fw(at_position: Vector2, data: Variant) -> bool: +func _can_drop_data_fw(_at_position: Vector2, data: Variant) -> bool: if typeof(data) == TYPE_DICTIONARY and data.has('files') and len(data.files) == 1: + if file_filter: + if '*.'+data.files[0].get_extension() in file_filter: return true + else: return true + return false -func _drop_data_fw(at_position: Vector2, data: Variant) -> void: - _on_file_dialog_selected(data.files[0]) + +func _drop_data_fw(_at_position: Vector2, data: Variant) -> void: + var file: String = data.files[0] + _on_file_dialog_selected(file) #endregion @@ -117,11 +127,13 @@ func _drop_data_fw(at_position: Vector2, data: Variant) -> void: #region VISUALS FOR FOCUS ################################################################################ -func _on_field_focus_entered(): +func _on_field_focus_entered() -> void: $FocusStyle.show() -func _on_field_focus_exited(): + +func _on_field_focus_exited() -> void: $FocusStyle.hide() - _on_file_dialog_selected(%Field.text) + var field_text: String = %Field.text + _on_file_dialog_selected(field_text) #endregion From 059556ce94ca060196f5d4359fb6b0a48ec1dba9 Mon Sep 17 00:00:00 2001 From: Cake Date: Tue, 27 Feb 2024 09:19:34 +0100 Subject: [PATCH 05/55] Add support for Layered Portraits in the editor. --- .../char_edit_p_section_main.gd | 107 ++++++++++++++++-- .../char_edit_p_section_main.tscn | 35 ++++-- 2 files changed, 124 insertions(+), 18 deletions(-) diff --git a/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.gd b/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.gd index 9a1c1867b..7eb0da63e 100644 --- a/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.gd +++ b/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.gd @@ -1,36 +1,121 @@ @tool +## Tab that allows setting a custom scene for a portrait. extends DialogicCharacterEditorPortraitSection -## Tab that allows setting a custom scene for a portrait. +const LAYERED_PORTRAIT_SCENE: String = "res://addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.tscn" func _get_title() -> String: return "Scene" -func _init(): + +func _init() -> void: hint_text = "You can use a custom scene for this portrait." + func _ready() -> void: %ScenePicker.file_filter = "*.tscn, *.scn; Scenes" - %ScenePicker.resource_icon = get_theme_icon('PackedScene', 'EditorIcons') - %ScenePicker.placeholder = 'Default scene' + %ScenePicker.resource_icon = get_theme_icon("PackedScene", "EditorIcons") + %ScenePicker.placeholder = "Default scene" + %ScenePicker.value_changed.connect(_on_scene_picker_value_changed) %OpenSceneButton.icon = get_theme_icon("ExternalLink", "EditorIcons") + %OpenSceneButton.pressed.connect(_on_open_scene_button_pressed) + + %MakeCustomButton.icon = get_theme_icon("ActionCopy", "EditorIcons") + %MakeCustomLayeredPortraitButton.pressed.connect(_on_make_custom_button_pressed) + + %PortraitDefaultOptions.item_selected.connect(_on_default_options_item_selected) func _load_portrait_data(data:Dictionary) -> void: - %ScenePicker.set_value(data.get('scene', '')) - %OpenSceneButton.visible = !data.get('scene', '').is_empty() + %ScenePicker.set_value(data.get("scene", "")) + %OpenSceneButton.visible = !data.get("scene", "").is_empty() + + +func _on_default_options_item_selected(index: int) -> void: + match index: + 0: + if not %ScenePicker.current_value.is_empty(): + %ScenePicker._set_value("") + + %ScenePicker.visible = false + %MakeCustomLayeredPortraitButton.hide() + 1: + #%ScenePicker._set_value(LAYERED_PORTRAIT_SCENE) + %ScenePicker.visible = false + %MakeCustomLayeredPortraitButton.show() -func _on_scene_picker_value_changed(prop_name:String, value:String) -> void: + 2: + %ScenePicker.visible = true + %MakeCustomLayeredPortraitButton.hide() + + update_preview.emit() + changed.emit() + + +func _on_scene_picker_value_changed(_prop_name: String, value: String) -> void: var data:Dictionary = selected_item.get_metadata(0) - data['scene'] = value + data["scene"] = value update_preview.emit() changed.emit() - %OpenSceneButton.visible = !data.get('scene', '').is_empty() + %OpenSceneButton.visible = !data.get("scene", "").is_empty() + + if value.is_empty(): + %PortraitDefaultOptions.select(0) + + elif value == LAYERED_PORTRAIT_SCENE: + %PortraitDefaultOptions.select(1) + + else: + %PortraitDefaultOptions.select(2) + var selected: int = %PortraitDefaultOptions.selected + _on_default_options_item_selected(selected) -func _on_open_scene_button_pressed(): - if !%ScenePicker.current_value.is_empty() and FileAccess.file_exists(%ScenePicker.current_value): + +func _on_open_scene_button_pressed() -> void: + var current_value: String = %ScenePicker.current_value + + if not current_value.is_empty() and FileAccess.file_exists(current_value): DialogicUtil.get_dialogic_plugin().get_editor_interface().open_scene_from_path(%ScenePicker.current_value) EditorInterface.set_main_screen_editor("2D") + + +func _on_make_custom_button_pressed() -> void: + find_parent("EditorView").godot_file_dialog( + _on_make_custom_layer_file_selected, + "", + EditorFileDialog.FILE_MODE_OPEN_DIR, + "Select folder for a new copy of portrait scene") + + +func _on_make_custom_layer_file_selected(target_path: String) -> void: + var original_file: String = %ScenePicker.current_value + make_scene_custom(original_file, target_path) + + +func make_scene_custom(previous_file: String, target_path: String) -> void: + + if not ResourceLoader.exists(previous_file): + printerr("[Dialogic] Unable to copy portrait scene from the invalid path:" + previous_file) + return + + var target_file := "custom_" + previous_file.get_file() + var target_file_path := target_path.path_join(target_file) + + + DirAccess.make_dir_absolute(target_path) + DirAccess.copy_absolute(previous_file, target_file_path) + + var file := FileAccess.open(target_file_path, FileAccess.READ) + var scene_text := file.get_as_text() + file.close() + + file = FileAccess.open(target_file_path, FileAccess.WRITE) + file.store_string(scene_text) + file.close() + + %ScenePicker._set_value(target_file_path) + + find_parent("EditorView").plugin_reference.get_editor_interface().get_resource_filesystem().scan_sources() diff --git a/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.tscn b/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.tscn index db355bd9f..985abb6b1 100644 --- a/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.tscn +++ b/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.tscn @@ -3,7 +3,7 @@ [ext_resource type="Script" path="res://addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.gd" id="1_ht8lu"] [ext_resource type="PackedScene" uid="uid://7mvxuaulctcq" path="res://addons/dialogic/Editor/Events/Fields/field_file.tscn" id="2_k8xs0"] -[sub_resource type="Image" id="Image_sbh6e"] +[sub_resource type="Image" id="Image_juoti"] data = { "data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0), "format": "RGBA8", @@ -12,8 +12,8 @@ data = { "width": 16 } -[sub_resource type="ImageTexture" id="ImageTexture_mbv6v"] -image = SubResource("Image_sbh6e") +[sub_resource type="ImageTexture" id="ImageTexture_83625"] +image = SubResource("Image_juoti") [node name="Scene" type="GridContainer"] offset_right = 298.0 @@ -21,22 +21,43 @@ offset_bottom = 86.0 size_flags_horizontal = 3 script = ExtResource("1_ht8lu") +[node name="PortraitDefaultOptions" type="OptionButton" parent="."] +unique_name_in_owner = true +layout_mode = 2 +item_count = 3 +selected = 0 +popup/item_0/text = "Default" +popup/item_0/id = 0 +popup/item_1/text = "Layered Portrait" +popup/item_1/id = 1 +popup/item_2/text = "Custom" +popup/item_2/id = 2 + [node name="HBox" type="HBoxContainer" parent="."] layout_mode = 2 size_flags_horizontal = 3 [node name="ScenePicker" parent="HBox" instance=ExtResource("2_k8xs0")] unique_name_in_owner = true +visible = false layout_mode = 2 size_flags_horizontal = 3 file_filter = "*.tscn, *.scn; Scenes" placeholder = "Default scene" -resource_icon = SubResource("ImageTexture_mbv6v") + +[node name="MakeCustomLayeredPortraitButton" type="Button" parent="HBox"] +unique_name_in_owner = true +visible = false +layout_mode = 2 +text = "Create Custom Layered Portrait" [node name="OpenSceneButton" type="Button" parent="HBox"] unique_name_in_owner = true layout_mode = 2 -icon = SubResource("ImageTexture_mbv6v") +icon = SubResource("ImageTexture_83625") -[connection signal="value_changed" from="HBox/ScenePicker" to="." method="_on_scene_picker_value_changed"] -[connection signal="pressed" from="HBox/OpenSceneButton" to="." method="_on_open_scene_button_pressed"] +[node name="MakeCustomButton" type="Button" parent="HBox"] +unique_name_in_owner = true +visible = false +layout_mode = 2 +icon = SubResource("ImageTexture_83625") From 8398802447e20e56b8f3cd839aa60e57d0dc84c2 Mon Sep 17 00:00:00 2001 From: Cake Date: Tue, 27 Feb 2024 15:29:16 +0100 Subject: [PATCH 06/55] Silently set scene picker current values. --- .../Editor/CharacterEditor/char_edit_p_section_main.gd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.gd b/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.gd index 7eb0da63e..69c949e6e 100644 --- a/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.gd +++ b/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.gd @@ -36,13 +36,13 @@ func _on_default_options_item_selected(index: int) -> void: match index: 0: if not %ScenePicker.current_value.is_empty(): - %ScenePicker._set_value("") + %ScenePicker.current_value = "" %ScenePicker.visible = false %MakeCustomLayeredPortraitButton.hide() 1: - #%ScenePicker._set_value(LAYERED_PORTRAIT_SCENE) + %ScenePicker.current_value = LAYERED_PORTRAIT_SCENE %ScenePicker.visible = false %MakeCustomLayeredPortraitButton.show() From 96ac2128677f9789838743fe6f51ca1843d8c7e0 Mon Sep 17 00:00:00 2001 From: Cake Date: Wed, 28 Feb 2024 13:26:35 +0100 Subject: [PATCH 07/55] Add mirroring support and fix transforms of child nodes breaking container position. --- .../LayeredPortrait/layered_portrait.gd | 53 +++++++++---------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd b/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd index 7e35711a0..10220d074 100644 --- a/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd +++ b/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd @@ -4,7 +4,6 @@ ## The parent class has a character and portrait variable. extends DialogicPortrait - const HIDE_COMMAND := "hide" const SHOW_COMMAND := "show" const SET_COMMAND := "set" @@ -20,21 +19,20 @@ func _update_portrait(passed_character: DialogicCharacter, passed_portrait: Stri apply_character_and_portrait(passed_character, passed_portrait) - +## Iterates over all children in [param start_node] and its children, looking +## for [class Sprite2D] nodes with a texture (not `null`). func _find_sprites_recursively(start_node: Node) -> Array[Sprite2D]: var sprites: Array[Sprite2D] = [] # Iterate through the children of the current node for child in start_node.get_children(): - if child is Sprite2D and child.texture != null: + if child is Sprite2D and child.texture: var sprite := child as Sprite2D sprites.append(sprite) continue - var child_sprites := _find_sprites_recursively(child) - # Extend the list of sprites with the sprites found in the child node sprites.append_array(child_sprites) return sprites @@ -42,15 +40,11 @@ func _find_sprites_recursively(start_node: Node) -> Array[Sprite2D]: func _ready() -> void: pass - #for sprite: Sprite2D in self._find_sprites_recursively(self): - # Get the sprite's height - # var sprite_height := sprite.texture.get_height() - # Set the offset to half of the sprite's height - #sprite.offset.y = -sprite_height ## A _command that will apply an effect to the layered portrait. class LayerCommand: + # The effect the command will apply. enum CommandType { SHOW_LAYER, HIDE_LAYER, @@ -65,7 +59,7 @@ class LayerCommand: var target_node := root.get_node(_path) if target_node == null: - print("Layered Portrait had no node matching the _path: ", _path) + print("Layered Portrait had no node matching the node path: ", _path) return match _type: @@ -143,28 +137,31 @@ func _set_extra_data(data: String) -> void: _command._execute(self) -func _get_covered_rect() -> Rect2: - var lowest_x: float = 0 - var lowest_y: float = 0 - var biggest_width: float = 0 - var biggest_height: float = 0 +func _set_mirror(is_mirrored: bool) -> void: + for sprite: Sprite2D in _find_sprites_recursively(self): + sprite.flip_h = is_mirrored + +func _find_largest_coverage_rect() -> Rect2: + var coverage_rect := Rect2(0, 0, 0, 0) - for sprite: Sprite2D in self._find_sprites_recursively(self): + for sprite: Sprite2D in _find_sprites_recursively(self): + var sprite_width := sprite.texture.get_width() + var sprite_height := sprite.texture.get_height() - if sprite.texture != null: - var rect: Rect2 = sprite.get_rect() + var texture_rect := Rect2( + sprite.position.x, + sprite.position.y, + sprite_width, + sprite_height + ) - if rect.position.x > lowest_x: - lowest_x = rect.position.x + coverage_rect = coverage_rect.merge(texture_rect) - if rect.position.y > lowest_y: - lowest_y = rect.position.y + return coverage_rect - if rect.size.x > biggest_width: - biggest_width = rect.size.x - if rect.size.y > biggest_height: - biggest_height = rect.size.y +func _get_covered_rect() -> Rect2: + var needed_rect := _find_largest_coverage_rect() - return Rect2(lowest_x, lowest_y, biggest_width, biggest_height) + return needed_rect From 1aab3a3b8db367bdaf91a1c62cce02d2932b484d Mon Sep 17 00:00:00 2001 From: Cake Date: Wed, 28 Feb 2024 13:26:56 +0100 Subject: [PATCH 08/55] Adjust comments. --- .../Modules/Character/dialogic_portrait.gd | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/addons/dialogic/Modules/Character/dialogic_portrait.gd b/addons/dialogic/Modules/Character/dialogic_portrait.gd index fdd7aba37..69165de35 100644 --- a/addons/dialogic/Modules/Character/dialogic_portrait.gd +++ b/addons/dialogic/Modules/Character/dialogic_portrait.gd @@ -12,10 +12,11 @@ var portrait: String ################################################################################ ## This function can be overridden. -## If this returns true, it won't insatnce a new scene, but call _update_portrait on this one. +## If this returns true, it won't instance a new scene, but call +## [method _update_portrait] on this one. ## This is only relevant if the next portrait uses the same scene. -## This allows implmenting transitions between portraits that use the same scene. -func _should_do_portrait_update(character:DialogicCharacter, portrait:String) -> bool: +## This allows implementing transitions between portraits that use the same scene. +func _should_do_portrait_update(character: DialogicCharacter, portrait: String) -> bool: return true @@ -25,17 +26,18 @@ func _should_do_portrait_update(character:DialogicCharacter, portrait:String) -> ## >>> $Sprite.position = $Sprite.get_rect().size * Vector2(-0.5, -1) ## ## * this depends on the portrait containers, but it will most likely be the bottom center (99% of cases) -func _update_portrait(passed_character:DialogicCharacter, passed_portrait:String) -> void: +func _update_portrait(passed_character: DialogicCharacter, passed_portrait: String) -> void: pass ## This should be implemented. It is used for sizing in the -## character editor preview and in portrait containers. -## Scale and offset will be applied by dialogic. -## For example for a simple sprite this should work: +## character editor preview and in portrait containers. +## Scale and offset will be applied by Dialogic. +## For example, a simple sprite: ## >>> return Rect2($Sprite.position, $Sprite.get_rect().size) ## -## This will only work as expected if the portrait is positioned so that the root is at the pivot point. +## This will only work as expected if the portrait is positioned so that the +## root is at the pivot point. ## ## If you've used apply_texture this should work automatically. func _get_covered_rect() -> Rect2: From 98f31087bf0cc8f63c8172b2fb96e3f9f6203486 Mon Sep 17 00:00:00 2001 From: Cake Date: Wed, 28 Feb 2024 15:12:00 +0100 Subject: [PATCH 09/55] Revert "Add support for Layered Portraits in the editor." This reverts commit 059556ce94ca060196f5d4359fb6b0a48ec1dba9. --- .../char_edit_p_section_main.gd | 107 ++---------------- .../char_edit_p_section_main.tscn | 35 ++---- 2 files changed, 18 insertions(+), 124 deletions(-) diff --git a/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.gd b/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.gd index 69c949e6e..9a1c1867b 100644 --- a/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.gd +++ b/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.gd @@ -1,121 +1,36 @@ @tool -## Tab that allows setting a custom scene for a portrait. extends DialogicCharacterEditorPortraitSection -const LAYERED_PORTRAIT_SCENE: String = "res://addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.tscn" +## Tab that allows setting a custom scene for a portrait. func _get_title() -> String: return "Scene" - -func _init() -> void: +func _init(): hint_text = "You can use a custom scene for this portrait." - func _ready() -> void: %ScenePicker.file_filter = "*.tscn, *.scn; Scenes" - %ScenePicker.resource_icon = get_theme_icon("PackedScene", "EditorIcons") - %ScenePicker.placeholder = "Default scene" - %ScenePicker.value_changed.connect(_on_scene_picker_value_changed) + %ScenePicker.resource_icon = get_theme_icon('PackedScene', 'EditorIcons') + %ScenePicker.placeholder = 'Default scene' %OpenSceneButton.icon = get_theme_icon("ExternalLink", "EditorIcons") - %OpenSceneButton.pressed.connect(_on_open_scene_button_pressed) - - %MakeCustomButton.icon = get_theme_icon("ActionCopy", "EditorIcons") - %MakeCustomLayeredPortraitButton.pressed.connect(_on_make_custom_button_pressed) - - %PortraitDefaultOptions.item_selected.connect(_on_default_options_item_selected) func _load_portrait_data(data:Dictionary) -> void: - %ScenePicker.set_value(data.get("scene", "")) - %OpenSceneButton.visible = !data.get("scene", "").is_empty() - - -func _on_default_options_item_selected(index: int) -> void: - match index: - 0: - if not %ScenePicker.current_value.is_empty(): - %ScenePicker.current_value = "" - - %ScenePicker.visible = false - %MakeCustomLayeredPortraitButton.hide() + %ScenePicker.set_value(data.get('scene', '')) + %OpenSceneButton.visible = !data.get('scene', '').is_empty() - 1: - %ScenePicker.current_value = LAYERED_PORTRAIT_SCENE - %ScenePicker.visible = false - %MakeCustomLayeredPortraitButton.show() - 2: - %ScenePicker.visible = true - %MakeCustomLayeredPortraitButton.hide() - - update_preview.emit() - changed.emit() - - -func _on_scene_picker_value_changed(_prop_name: String, value: String) -> void: +func _on_scene_picker_value_changed(prop_name:String, value:String) -> void: var data:Dictionary = selected_item.get_metadata(0) - data["scene"] = value + data['scene'] = value update_preview.emit() changed.emit() - %OpenSceneButton.visible = !data.get("scene", "").is_empty() - - if value.is_empty(): - %PortraitDefaultOptions.select(0) - - elif value == LAYERED_PORTRAIT_SCENE: - %PortraitDefaultOptions.select(1) - - else: - %PortraitDefaultOptions.select(2) + %OpenSceneButton.visible = !data.get('scene', '').is_empty() - var selected: int = %PortraitDefaultOptions.selected - _on_default_options_item_selected(selected) - -func _on_open_scene_button_pressed() -> void: - var current_value: String = %ScenePicker.current_value - - if not current_value.is_empty() and FileAccess.file_exists(current_value): +func _on_open_scene_button_pressed(): + if !%ScenePicker.current_value.is_empty() and FileAccess.file_exists(%ScenePicker.current_value): DialogicUtil.get_dialogic_plugin().get_editor_interface().open_scene_from_path(%ScenePicker.current_value) EditorInterface.set_main_screen_editor("2D") - - -func _on_make_custom_button_pressed() -> void: - find_parent("EditorView").godot_file_dialog( - _on_make_custom_layer_file_selected, - "", - EditorFileDialog.FILE_MODE_OPEN_DIR, - "Select folder for a new copy of portrait scene") - - -func _on_make_custom_layer_file_selected(target_path: String) -> void: - var original_file: String = %ScenePicker.current_value - make_scene_custom(original_file, target_path) - - -func make_scene_custom(previous_file: String, target_path: String) -> void: - - if not ResourceLoader.exists(previous_file): - printerr("[Dialogic] Unable to copy portrait scene from the invalid path:" + previous_file) - return - - var target_file := "custom_" + previous_file.get_file() - var target_file_path := target_path.path_join(target_file) - - - DirAccess.make_dir_absolute(target_path) - DirAccess.copy_absolute(previous_file, target_file_path) - - var file := FileAccess.open(target_file_path, FileAccess.READ) - var scene_text := file.get_as_text() - file.close() - - file = FileAccess.open(target_file_path, FileAccess.WRITE) - file.store_string(scene_text) - file.close() - - %ScenePicker._set_value(target_file_path) - - find_parent("EditorView").plugin_reference.get_editor_interface().get_resource_filesystem().scan_sources() diff --git a/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.tscn b/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.tscn index 985abb6b1..db355bd9f 100644 --- a/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.tscn +++ b/addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.tscn @@ -3,7 +3,7 @@ [ext_resource type="Script" path="res://addons/dialogic/Editor/CharacterEditor/char_edit_p_section_main.gd" id="1_ht8lu"] [ext_resource type="PackedScene" uid="uid://7mvxuaulctcq" path="res://addons/dialogic/Editor/Events/Fields/field_file.tscn" id="2_k8xs0"] -[sub_resource type="Image" id="Image_juoti"] +[sub_resource type="Image" id="Image_sbh6e"] data = { "data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0), "format": "RGBA8", @@ -12,8 +12,8 @@ data = { "width": 16 } -[sub_resource type="ImageTexture" id="ImageTexture_83625"] -image = SubResource("Image_juoti") +[sub_resource type="ImageTexture" id="ImageTexture_mbv6v"] +image = SubResource("Image_sbh6e") [node name="Scene" type="GridContainer"] offset_right = 298.0 @@ -21,43 +21,22 @@ offset_bottom = 86.0 size_flags_horizontal = 3 script = ExtResource("1_ht8lu") -[node name="PortraitDefaultOptions" type="OptionButton" parent="."] -unique_name_in_owner = true -layout_mode = 2 -item_count = 3 -selected = 0 -popup/item_0/text = "Default" -popup/item_0/id = 0 -popup/item_1/text = "Layered Portrait" -popup/item_1/id = 1 -popup/item_2/text = "Custom" -popup/item_2/id = 2 - [node name="HBox" type="HBoxContainer" parent="."] layout_mode = 2 size_flags_horizontal = 3 [node name="ScenePicker" parent="HBox" instance=ExtResource("2_k8xs0")] unique_name_in_owner = true -visible = false layout_mode = 2 size_flags_horizontal = 3 file_filter = "*.tscn, *.scn; Scenes" placeholder = "Default scene" - -[node name="MakeCustomLayeredPortraitButton" type="Button" parent="HBox"] -unique_name_in_owner = true -visible = false -layout_mode = 2 -text = "Create Custom Layered Portrait" +resource_icon = SubResource("ImageTexture_mbv6v") [node name="OpenSceneButton" type="Button" parent="HBox"] unique_name_in_owner = true layout_mode = 2 -icon = SubResource("ImageTexture_83625") +icon = SubResource("ImageTexture_mbv6v") -[node name="MakeCustomButton" type="Button" parent="HBox"] -unique_name_in_owner = true -visible = false -layout_mode = 2 -icon = SubResource("ImageTexture_83625") +[connection signal="value_changed" from="HBox/ScenePicker" to="." method="_on_scene_picker_value_changed"] +[connection signal="pressed" from="HBox/OpenSceneButton" to="." method="_on_open_scene_button_pressed"] From 84d3aeb88606714c76cf392ca330b2d568d12ab8 Mon Sep 17 00:00:00 2001 From: Cake Date: Sun, 3 Mar 2024 15:34:42 +0100 Subject: [PATCH 10/55] Fix position calculation. --- .../Modules/Character/LayeredPortrait/layered_portrait.gd | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd b/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd index 10220d074..8a46ede31 100644 --- a/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd +++ b/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd @@ -149,6 +149,10 @@ func _find_largest_coverage_rect() -> Rect2: var sprite_width := sprite.texture.get_width() var sprite_height := sprite.texture.get_height() + sprite.scale = Vector2.ONE + sprite.centered = false + sprite.position = sprite.get_rect().size * Vector2(-0.5, -1) + var texture_rect := Rect2( sprite.position.x, sprite.position.y, From 631ab98ad31a753a8a0c7fd5082cd2ab29d15f45 Mon Sep 17 00:00:00 2001 From: Cake Date: Sun, 3 Mar 2024 18:21:29 +0100 Subject: [PATCH 11/55] Improve position system. --- .../Character/LayeredPortrait/layered_portrait.gd | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd b/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd index 8a46ede31..ad870a744 100644 --- a/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd +++ b/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd @@ -18,7 +18,6 @@ static var REGEX := RegEx.create_from_string(REGEX_STRING) func _update_portrait(passed_character: DialogicCharacter, passed_portrait: String) -> void: apply_character_and_portrait(passed_character, passed_portrait) - ## Iterates over all children in [param start_node] and its children, looking ## for [class Sprite2D] nodes with a texture (not `null`). func _find_sprites_recursively(start_node: Node) -> Array[Sprite2D]: @@ -39,7 +38,10 @@ func _find_sprites_recursively(start_node: Node) -> Array[Sprite2D]: func _ready() -> void: - pass + for sprite: Sprite2D in _find_sprites_recursively(self): + sprite.scale = Vector2.ONE + sprite.centered = false + sprite.position = (sprite.get_rect().size * Vector2(-0.5, -1)) + sprite.position ## A _command that will apply an effect to the layered portrait. @@ -149,10 +151,6 @@ func _find_largest_coverage_rect() -> Rect2: var sprite_width := sprite.texture.get_width() var sprite_height := sprite.texture.get_height() - sprite.scale = Vector2.ONE - sprite.centered = false - sprite.position = sprite.get_rect().size * Vector2(-0.5, -1) - var texture_rect := Rect2( sprite.position.x, sprite.position.y, From 754f157ee8df44e6ced63a3b6ec91d42d94b7463 Mon Sep 17 00:00:00 2001 From: Cake Date: Mon, 4 Mar 2024 09:16:17 +0100 Subject: [PATCH 12/55] Fix typo. --- addons/dialogic/Modules/StyleEditor/style_editor.tscn | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/addons/dialogic/Modules/StyleEditor/style_editor.tscn b/addons/dialogic/Modules/StyleEditor/style_editor.tscn index 193606bb4..edcc87ee2 100644 --- a/addons/dialogic/Modules/StyleEditor/style_editor.tscn +++ b/addons/dialogic/Modules/StyleEditor/style_editor.tscn @@ -84,16 +84,16 @@ text = "Styles" [node name="HintTooltip" parent="Panel/VBox/Title" instance=ExtResource("2_g4mnt")] layout_mode = 2 -tooltip_text = "Each style consist of a list of layers and settings for each layer. +tooltip_text = "Each style consist of a list of layers and settings for each layer. A style can inherit from another style (inherited styles can only overwrite settings of their layers). When one style is selected as default dialogic will use that style when calling Dialogic.start() otherwise a fallback is used. -You can change the style with the Change Style event, by setting a style on a character or by calling +You can change the style with the Change Style event, by setting a style on a character or by calling Dialogic.Styles.load_style(\"Style Name\") before calling Dialogic.start()." texture = SubResource("ImageTexture_rfmv2") -hint_text = "Each style consist of a list of layers and settings for each layer. +hint_text = "Each style consist of a list of layers and settings for each layer. A style can inherit from another style (inherited styles can only overwrite settings of their layers). When one style is selected as default dialogic will use that style when calling Dialogic.start() otherwise a fallback is used. -You can change the style with the Change Style event, by setting a style on a character or by calling +You can change the style with the Change Style event, by setting a style on a character or by calling Dialogic.Styles.load_style(\"Style Name\") before calling Dialogic.start()." [node name="StyleButtons" type="HBoxContainer" parent="Panel/VBox"] @@ -405,7 +405,7 @@ alignment = 1 [node name="Label" type="Label" parent="NoStyleView"] layout_mode = 2 -text = "You have not setup any style. Dialogic will use a fallback style." +text = "You have not set up any styles yet. Dialogic will use a fallback style." horizontal_alignment = 1 autowrap_mode = 3 From 23ba2c5822d927e13d8cb54ece92ea33792983ee Mon Sep 17 00:00:00 2001 From: Cake Date: Mon, 4 Mar 2024 11:29:37 +0100 Subject: [PATCH 13/55] Fix preview when switching scenes. --- .../CharacterEditor/character_editor.gd | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/addons/dialogic/Editor/CharacterEditor/character_editor.gd b/addons/dialogic/Editor/CharacterEditor/character_editor.gd index 9d6f4e10e..552fd625d 100644 --- a/addons/dialogic/Editor/CharacterEditor/character_editor.gd +++ b/addons/dialogic/Editor/CharacterEditor/character_editor.gd @@ -9,7 +9,8 @@ signal portrait_selected() # Current state var loading := false -var current_previewed_scene = null +var current_previewed_scene: Variant = null +var current_scene_path: String = "" # References var selected_item: TreeItem @@ -536,24 +537,28 @@ func report_name_change(item:TreeItem) -> void: ########### PREVIEW ############################################################ #region Preview -func update_preview(force:=false) -> void: +func update_preview(force := false) -> void: %ScenePreviewWarning.hide() + if selected_item and is_instance_valid(selected_item) and selected_item.get_metadata(0) != null and !selected_item.get_metadata(0).has('group'): %PreviewLabel.text = 'Preview of "'+%PortraitTree.get_full_item_name(selected_item)+'"' var current_portrait_data: Dictionary = selected_item.get_metadata(0) if not force and current_previewed_scene != null \ - and current_previewed_scene.get_meta('path', '') == current_portrait_data.get('scene') \ + and scene_file_path == current_portrait_data.get('scene') \ and current_previewed_scene.has_method('_should_do_portrait_update') \ and is_instance_valid(current_previewed_scene.get_script()) \ and current_previewed_scene._should_do_portrait_update(current_resource, selected_item.get_text(0)): - pass # we keep the same scene + # We keep the same scene. + pass else: + for node in %RealPreviewPivot.get_children(): node.queue_free() current_previewed_scene = null + current_scene_path = "" var scene_path := def_portrait_path if not current_portrait_data.get('scene', '').is_empty(): @@ -561,11 +566,12 @@ func update_preview(force:=false) -> void: if FileAccess.file_exists(scene_path): current_previewed_scene = load(scene_path).instantiate() + current_scene_path = scene_path - if current_previewed_scene: + if not current_previewed_scene == null: %RealPreviewPivot.add_child(current_previewed_scene) - if current_previewed_scene != null: + if not current_previewed_scene == null: var scene: Node = current_previewed_scene scene.show_behind_parent = true @@ -573,16 +579,20 @@ func update_preview(force:=false) -> void: var mirror: bool = current_portrait_data.get('mirror', false) != current_resource.mirror var scale: float = current_portrait_data.get('scale', 1) * current_resource.scale + if current_portrait_data.get('ignore_char_scale', false): scale = current_portrait_data.get('scale', 1) + var offset: Vector2 = current_portrait_data.get('offset', Vector2()) + current_resource.offset if is_instance_valid(scene.get_script()) and scene.script.is_tool(): + if scene.has_method('_update_portrait'): ## Create a fake duplicate resource that has all the portrait changes applied already var preview_character := current_resource.duplicate() preview_character.portraits = get_updated_portrait_dict() scene._update_portrait(preview_character, %PortraitTree.get_full_item_name(selected_item)) + if scene.has_method('_set_mirror'): scene._set_mirror(mirror) @@ -590,25 +600,33 @@ func update_preview(force:=false) -> void: scene.position = Vector2() + offset scene.scale = Vector2(1,1)*scale else: - if is_instance_valid(scene.get_script()) and scene.script.is_tool() and scene.has_method('_get_covered_rect'): - var rect: Rect2= scene._get_covered_rect() + + if not scene.get_script() == null and scene.script.is_tool() and scene.has_method('_get_covered_rect'): + var rect: Rect2 = scene._get_covered_rect() var available_rect: Rect2 = %FullPreviewAvailableRect.get_rect() scene.scale = Vector2(1,1) * min(available_rect.size.x/rect.size.x, available_rect.size.y/rect.size.y) %RealPreviewPivot.position = (rect.position)*-1*scene.scale %RealPreviewPivot.position.x = %FullPreviewAvailableRect.size.x/2 scene.position = Vector2() + else: %ScenePreviewWarning.show() else: %PreviewLabel.text = 'Nothing to preview' + for child in %PortraitSettingsSection.get_children(): + if child is DialogicCharacterEditorPortraitSection: child._recheck(current_portrait_data) + else: %PreviewLabel.text = 'No portrait to preview.' + for node in %RealPreviewPivot.get_children(): node.queue_free() + current_previewed_scene = null + current_scene_path = "" func _on_some_resource_saved(file:Variant) -> void: From e8d8a115ab01ceeaa4828081de1cffa3a7560c55 Mon Sep 17 00:00:00 2001 From: Cake Date: Mon, 4 Mar 2024 11:31:41 +0100 Subject: [PATCH 14/55] Fix portrait updating. --- .../LayeredPortrait/layered_portrait.gd | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd b/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd index ad870a744..1215edbd9 100644 --- a/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd +++ b/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd @@ -13,22 +13,32 @@ static var OPERATORS_EXPRESSION := "|".join(OPERATORS) static var REGEX_STRING := "(" + OPERATORS_EXPRESSION + ") (\\S+)" static var REGEX := RegEx.create_from_string(REGEX_STRING) +var _initialized := false ## Load anything related to the given character and portrait func _update_portrait(passed_character: DialogicCharacter, passed_portrait: String) -> void: + + if not _initialized: + for sprite: Sprite2D in _find_sprites_recursively(self): + sprite.scale = Vector2.ONE + sprite.centered = false + sprite.position = (sprite.get_rect().size * Vector2(-0.5, -1)) + sprite.position + + _initialized = true + apply_character_and_portrait(passed_character, passed_portrait) + ## Iterates over all children in [param start_node] and its children, looking ## for [class Sprite2D] nodes with a texture (not `null`). func _find_sprites_recursively(start_node: Node) -> Array[Sprite2D]: var sprites: Array[Sprite2D] = [] # Iterate through the children of the current node - for child in start_node.get_children(): + for child: Node in start_node.get_children(): if child is Sprite2D and child.texture: - var sprite := child as Sprite2D - sprites.append(sprite) + sprites.append(child) continue var child_sprites := _find_sprites_recursively(child) @@ -37,13 +47,6 @@ func _find_sprites_recursively(start_node: Node) -> Array[Sprite2D]: return sprites -func _ready() -> void: - for sprite: Sprite2D in _find_sprites_recursively(self): - sprite.scale = Vector2.ONE - sprite.centered = false - sprite.position = (sprite.get_rect().size * Vector2(-0.5, -1)) + sprite.position - - ## A _command that will apply an effect to the layered portrait. class LayerCommand: # The effect the command will apply. @@ -64,20 +67,26 @@ class LayerCommand: print("Layered Portrait had no node matching the node path: ", _path) return + if target_node is Sprite2D: + print("Layered Portrait target path '", _path, "', is not a Sprite2D node.") + return + + var sprite := target_node as Sprite2D + match _type: CommandType.SHOW_LAYER: - target_node.show() + sprite.show() CommandType.HIDE_LAYER: - target_node.hide() + sprite.hide() CommandType.SET_LAYER: var target_parent := target_node.get_parent() for child in target_parent.get_children(): - child.hide() + sprite.hide() - target_node.show() + sprite.show() ## Turns the input into a single [class LayerCommand] object. From 5a6dda1a05e7c7c263ac674f68980c525ff3fcd1 Mon Sep 17 00:00:00 2001 From: Cake Date: Wed, 6 Mar 2024 22:57:15 +0100 Subject: [PATCH 15/55] Fix `Sprite2D` logic check. --- .../Modules/Character/LayeredPortrait/layered_portrait.gd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd b/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd index 1215edbd9..1ed36ffe7 100644 --- a/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd +++ b/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd @@ -67,7 +67,7 @@ class LayerCommand: print("Layered Portrait had no node matching the node path: ", _path) return - if target_node is Sprite2D: + if not target_node is Sprite2D: print("Layered Portrait target path '", _path, "', is not a Sprite2D node.") return From dd3b3e45e56a4e7191a1ad97dc359c9ffb06c943 Mon Sep 17 00:00:00 2001 From: Cake Date: Wed, 6 Mar 2024 23:16:05 +0100 Subject: [PATCH 16/55] Hide all other nodes. --- .../Modules/Character/LayeredPortrait/layered_portrait.gd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd b/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd index 1ed36ffe7..17b3be5a2 100644 --- a/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd +++ b/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd @@ -84,7 +84,7 @@ class LayerCommand: var target_parent := target_node.get_parent() for child in target_parent.get_children(): - sprite.hide() + child.hide() sprite.show() From 564f0ec58a4a15e35429eecbc1cc1033cd9a6e79 Mon Sep 17 00:00:00 2001 From: Cake Date: Thu, 7 Mar 2024 00:21:22 +0100 Subject: [PATCH 17/55] Apply some code improvements. --- .../LayeredPortrait/layered_portrait.gd | 89 +++++++++++++------ 1 file changed, 64 insertions(+), 25 deletions(-) diff --git a/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd b/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd index 17b3be5a2..9aae63df6 100644 --- a/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd +++ b/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd @@ -4,42 +4,62 @@ ## The parent class has a character and portrait variable. extends DialogicPortrait -const HIDE_COMMAND := "hide" -const SHOW_COMMAND := "show" -const SET_COMMAND := "set" +## The term used for hiding a layer. +const _HIDE_COMMAND := "hide" +## The term used for showing a layer. +const _SHOW_COMMAND := "show" +## The term used for setting a layer to be the only visible layer. +const _SET_COMMAND := "set" -const OPERATORS = [HIDE_COMMAND, SHOW_COMMAND, SET_COMMAND] -static var OPERATORS_EXPRESSION := "|".join(OPERATORS) -static var REGEX_STRING := "(" + OPERATORS_EXPRESSION + ") (\\S+)" -static var REGEX := RegEx.create_from_string(REGEX_STRING) +## A collection of all possible layer commands. +const _OPERATORS = [_HIDE_COMMAND, _SHOW_COMMAND, _SET_COMMAND] + +static var _OPERATORS_EXPRESSION := "|".join(_OPERATORS) +static var _REGEX_STRING := "(" + _OPERATORS_EXPRESSION + ") (\\S+)" +static var _REGEX := RegEx.create_from_string(_REGEX_STRING) var _initialized := false + +## Overriding [class DialogicPortrait]'s method. +## ## Load anything related to the given character and portrait func _update_portrait(passed_character: DialogicCharacter, passed_portrait: String) -> void: if not _initialized: - for sprite: Sprite2D in _find_sprites_recursively(self): - sprite.scale = Vector2.ONE - sprite.centered = false - sprite.position = (sprite.get_rect().size * Vector2(-0.5, -1)) + sprite.position - + _apply_layer_adjustments() _initialized = true apply_character_and_portrait(passed_character, passed_portrait) +## Modifies all layers to fit the portrait preview and appear correctly in +## portrait containers. +## +## This method is not changing the scene itself and is intended for the +## Dialogic editor preview and in-game rendering only. +func _apply_layer_adjustments() -> void: + for sprite: Sprite2D in _find_sprites_recursively(self): + sprite.scale = Vector2.ONE + sprite.centered = false + sprite.position = (sprite.get_rect().size * Vector2(-0.5, -1)) + sprite.position + + ## Iterates over all children in [param start_node] and its children, looking ## for [class Sprite2D] nodes with a texture (not `null`). +## All found sprites are returned in an array, eventually returning all +## sprites in the scene. func _find_sprites_recursively(start_node: Node) -> Array[Sprite2D]: var sprites: Array[Sprite2D] = [] # Iterate through the children of the current node for child: Node in start_node.get_children(): - if child is Sprite2D and child.texture: - sprites.append(child) - continue + if child is Sprite2D: + var sprite := child as Sprite2D + + if sprite.texture: + sprites.append(sprite) var child_sprites := _find_sprites_recursively(child) sprites.append_array(child_sprites) @@ -47,28 +67,32 @@ func _find_sprites_recursively(start_node: Node) -> Array[Sprite2D]: return sprites -## A _command that will apply an effect to the layered portrait. +## A command will apply an effect to the layered portrait. class LayerCommand: - # The effect the command will apply. + # The different types of effects. enum CommandType { + ## Additively Show a specific layer. SHOW_LAYER, + ## Subtractively hide a specific layer. HIDE_LAYER, + ## Exclusively show a specific layer, hiding all other sibling layers. + ## A sibling layer is a layer sharing the same parent node. SET_LAYER, } var _path: String var _type: CommandType - ## Executes the _command. + ## Executes the effect of the layer based on the [enum CommandType]. func _execute(root: Node) -> void: var target_node := root.get_node(_path) if target_node == null: - print("Layered Portrait had no node matching the node path: ", _path) + printerr("Layered Portrait had no node matching the node path: '", _path, "'.") return if not target_node is Sprite2D: - print("Layered Portrait target path '", _path, "', is not a Sprite2D node.") + printerr("Layered Portrait target path '", _path, "', is not a Sprite2D node.") return var sprite := target_node as Sprite2D @@ -83,8 +107,11 @@ class LayerCommand: CommandType.SET_LAYER: var target_parent := target_node.get_parent() - for child in target_parent.get_children(): - child.hide() + for child: Node in target_parent.get_children(): + + if child is Sprite2D: + var sprite_child := child as Sprite2D + sprite_child.hide() sprite.show() @@ -92,9 +119,7 @@ class LayerCommand: ## Turns the input into a single [class LayerCommand] object. ## Returns `null` if the input cannot be parsed into a [class LayerCommand]. func _parse_layer_command(input: String) -> LayerCommand: - var command := LayerCommand.new() - - var regex_match: RegExMatch = REGEX.search(input) + var regex_match: RegExMatch = _REGEX.search(input) if regex_match == null: print("Layered Portrait had an invalid command: ", input) @@ -103,6 +128,8 @@ func _parse_layer_command(input: String) -> LayerCommand: var _path: String = regex_match.get_string(2) var operator: String = regex_match.get_string(1) + var command := LayerCommand.new() + match operator: SET_COMMAND: command._type = LayerCommand.CommandType.SET_LAYER @@ -140,6 +167,9 @@ func _parse_input_to_layer_commands(input: String) -> Array[LayerCommand]: return commands + +## Overriding [class DialogicPortrait]'s method. +## ## The extra data will be turned into layer commands and then be executed. func _set_extra_data(data: String) -> void: var commands := _parse_input_to_layer_commands(data) @@ -148,11 +178,16 @@ func _set_extra_data(data: String) -> void: _command._execute(self) +## Overriding [class DialogicPortrait]'s method. +## +## Handling all layers horizontal flip state. func _set_mirror(is_mirrored: bool) -> void: for sprite: Sprite2D in _find_sprites_recursively(self): sprite.flip_h = is_mirrored +## Scans all nodes in this scene and finds the largest rectangle that +## covers encloses every sprite. func _find_largest_coverage_rect() -> Rect2: var coverage_rect := Rect2(0, 0, 0, 0) @@ -172,6 +207,10 @@ func _find_largest_coverage_rect() -> Rect2: return coverage_rect +## Overriding [class DialogicPortrait]'s method. +## +## Called by Dialogic when the portrait is needed to be shown. +## For instance, in the Dialogic editor or in-game. func _get_covered_rect() -> Rect2: var needed_rect := _find_largest_coverage_rect() From 79f9656ff6f849fb8bdf6a6820505d3e0bc86957 Mon Sep 17 00:00:00 2001 From: Cake Date: Thu, 7 Mar 2024 23:55:02 +0100 Subject: [PATCH 18/55] Fix command type matching. --- .../Modules/Character/LayeredPortrait/layered_portrait.gd | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd b/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd index 9aae63df6..a6ff1aa3a 100644 --- a/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd +++ b/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd @@ -131,16 +131,16 @@ func _parse_layer_command(input: String) -> LayerCommand: var command := LayerCommand.new() match operator: - SET_COMMAND: + _SET_COMMAND: command._type = LayerCommand.CommandType.SET_LAYER - SHOW_COMMAND: + _SHOW_COMMAND: command._type = LayerCommand.CommandType.SHOW_LAYER - HIDE_COMMAND: + _HIDE_COMMAND: command._type = LayerCommand.CommandType.HIDE_LAYER - SET_COMMAND: + _SET_COMMAND: command._type = LayerCommand.CommandType.SET_LAYER ## We clean escape symbols and trim the spaces. From b83a366f1ad2b211b23a7d277e4ccac0a1a9aee1 Mon Sep 17 00:00:00 2001 From: Cake Date: Sat, 9 Mar 2024 04:56:34 +0100 Subject: [PATCH 19/55] Fix skipping transform on character update. --- addons/dialogic/Modules/Character/event_character.gd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/dialogic/Modules/Character/event_character.gd b/addons/dialogic/Modules/Character/event_character.gd index 62d633e85..63584948c 100644 --- a/addons/dialogic/Modules/Character/event_character.gd +++ b/addons/dialogic/Modules/Character/event_character.gd @@ -122,7 +122,7 @@ func _execute() -> void: dialogic.Portraits.change_character_extradata(character, extra_data) if set_portrait: - dialogic.Portraits.change_character_portrait(character, portrait, false) + dialogic.Portraits.change_character_portrait(character, portrait) if set_mirrored: dialogic.Portraits.change_character_mirror(character, mirrored) @@ -271,7 +271,7 @@ func from_text(string:String) -> void: set_position = true if result.get_string('shortcode'): - var shortcode_params = parse_shortcode_parameters(result.get_string('shortcode')) + var shortcode_params := parse_shortcode_parameters(result.get_string('shortcode')) animation_name = shortcode_params.get('animation', '') var animLength = shortcode_params.get('length', '0.5').to_float() From dc0776549ed1391a202c1d75e80e09e3ae1933f2 Mon Sep 17 00:00:00 2001 From: Cake Date: Sat, 9 Mar 2024 04:57:05 +0100 Subject: [PATCH 20/55] Update code of `DialogicAnimation`. --- .../Character/class_dialogic_animation.gd | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/addons/dialogic/Modules/Character/class_dialogic_animation.gd b/addons/dialogic/Modules/Character/class_dialogic_animation.gd index b050c0286..995849efd 100644 --- a/addons/dialogic/Modules/Character/class_dialogic_animation.gd +++ b/addons/dialogic/Modules/Character/class_dialogic_animation.gd @@ -7,41 +7,43 @@ signal finished_once signal finished ## Set at runtime, will be the node to animate. -var node :Node +var node: Node ## Set at runtime, will be the length of the animation. -var time : float +var time: float ## Set at runtime, will be the position at which to end the animation. -var end_position : Vector2 +var end_position: Vector2 ## Set at runtime. The position the node started at. -var orig_pos : Vector2 +var orig_pos: Vector2 ## Used to repeate the animation for a number of times. -var repeats : int +var repeats: int -func _ready(): - connect('finished_once', finished_one_loop) +func _ready() -> void: + finished_once.connect(finished_one_loop) ## To be overridden. Do the actual animating/tweening in here. -## Use the properties [node], [time], [end_position], [orig_pos]. -func animate(): +## Use the properties [member node], [member time], [member end_position], [member orig_pos]. +func animate() -> void: pass -func finished_one_loop(): +func finished_one_loop() -> void: repeats -= 1 + if repeats > 0: animate() - elif repeats == 0: - emit_signal("finished") + + else: + finished.emit() -func pause(): +func pause() -> void: if node: node.process_mode = Node.PROCESS_MODE_DISABLED -func resume(): +func resume() -> void: if node: node.process_mode = Node.PROCESS_MODE_INHERIT From 8834719604c7d0e9b1004cffaf08914094d839a8 Mon Sep 17 00:00:00 2001 From: Cake Date: Sat, 9 Mar 2024 05:02:27 +0100 Subject: [PATCH 21/55] Rework removal portrait scenes. This will use portrait nodes and allows to queue up an animation on removal. --- .../Modules/Character/subsystem_portraits.gd | 111 ++++++++++++------ 1 file changed, 74 insertions(+), 37 deletions(-) diff --git a/addons/dialogic/Modules/Character/subsystem_portraits.gd b/addons/dialogic/Modules/Character/subsystem_portraits.gd index 6c836f2b5..3974a77fd 100644 --- a/addons/dialogic/Modules/Character/subsystem_portraits.gd +++ b/addons/dialogic/Modules/Character/subsystem_portraits.gd @@ -81,8 +81,25 @@ func _create_character_node(character:DialogicCharacter, container:DialogicNode_ return character_node +## Instead of instantly freeing the portrait scene, we will play an animation +## and then free it. +## This allows for cross-fade effects and other animations. +## +## If [param duration_seconds] is `0.0`, the portrait will be removed instantly. +func _remove_portrait_timed(portrait_node: Node, duration_seconds: float) -> void: + + + if duration_seconds > 0: + # TODO: Allow setting the animation + var animation_name := DialogicResourceUtil.guess_special_resource("PortraitAnimation", "Fade Out", "") + var animation := _animate_portrait(portrait_node, animation_name, duration_seconds) + await animation.finished + + portrait_node.queue_free() + + # Changes the portrait of a specific [character node]. -func _change_portrait(character_node:Node2D, portrait:String, update_transform:=true) -> Dictionary: +func _change_portrait(character_node: Node2D, portrait: String, update_transform := true) -> Dictionary: var character: DialogicCharacter = character_node.get_meta('character') if portrait.is_empty(): portrait = character.default_portrait @@ -107,10 +124,9 @@ func _change_portrait(character_node:Node2D, portrait:String, update_transform:= info['same_scene'] = true else: - # remove previous portrait - if character_node.get_child_count(): - character_node.get_child(0).queue_free() - character_node.remove_child(character_node.get_child(0)) + # Start removal of the current portrait. + if character_node.get_child_count() > 0: + _remove_portrait(character_node.get_child(0), 2.0) if ResourceLoader.exists(scene_path): var packed_scene: PackedScene = load(scene_path) @@ -136,7 +152,7 @@ func _change_portrait(character_node:Node2D, portrait:String, update_transform:= character_node.add_child(portrait_node) if update_transform: - _update_portrait_transform(character_node) + _update_portrait_transform(portrait_node) return info @@ -156,10 +172,10 @@ func _change_portrait_extradata(character_node:Node2D, extra_data:="") -> void: character_node.get_child(0)._set_extra_data(extra_data) -func _update_portrait_transform(character_node:Node2D, time:float = 0.0) -> void: - var character: DialogicCharacter= character_node.get_meta('character') +func _update_portrait_transform(portrait_node: Node, time:float = 0.0) -> void: + var character_node: Node = portrait_node.get_parent() - var portrait_node: Node = character_node.get_child(0) + var character: DialogicCharacter = character_node.get_meta('character') var portrait_info: Dictionary = character.portraits.get(character_node.get_meta('portrait'), {}) # ignore the character scale on custom portraits that have 'ignore_char_scale' set to true @@ -188,10 +204,13 @@ func _update_portrait_transform(character_node:Node2D, time:float = 0.0) -> void ## Animates the portrait in the given container with the given animation. -func _animate_portrait(character_node:Node2D, animation_path:String, length:float, repeats := 1) -> DialogicAnimation: +func _animate_portrait(character_node: Node2D, animation_path: String, length: float, repeats := 1) -> DialogicAnimation: if character_node.has_meta('animation_node') and is_instance_valid(character_node.get_meta('animation_node')): character_node.get_meta('animation_node').queue_free() + # Animate the portrait node. + character_node = character_node.get_child(-1) + var anim_script: Script = load(animation_path) var anim_node := Node.new() anim_node.set_script(anim_script) @@ -209,15 +228,17 @@ func _animate_portrait(character_node:Node2D, animation_path:String, length:floa ## Moves the given portrait to the given container. -func _move_portrait(character_node:Node2D, portrait_container:DialogicNode_PortraitContainer, time:= 0.0) -> void: +func _move_portrait(portrait_node: Node2D, portrait_container:DialogicNode_PortraitContainer, time:= 0.0) -> void: + var global_pos := portrait_node.global_position + + if portrait_node.get_parent(): + portrait_node.get_parent().remove_child(portrait_node) - var global_pos := character_node.global_position - if character_node.get_parent(): character_node.get_parent().remove_child(character_node) + portrait_container.add_child(portrait_node) - portrait_container.add_child(character_node) + portrait_node.position = global_pos - portrait_node.get_parent().global_position - character_node.position = global_pos-character_node.get_parent().global_position - _update_portrait_transform(character_node, time) + _update_portrait_transform(portrait_node.get_child(-1), time) ## Changes the given portraits z_index. @@ -239,9 +260,12 @@ func z_sort_portrait_containers(con1:DialogicNode_PortraitContainer, con2:Dialog return false -func _remove_portrait(character_node:Node2D) -> void: - character_node.get_parent().remove_child(character_node) - character_node.queue_free() +## Private method to remove a [param portrait_node]. +## +## Accepts optional [duration_seconds] to remove the portrait over a span +## of time. +func _remove_portrait(portrait_node: Node2D, duration_seconds := 1.0) -> void: + _remove_portrait_timed(portrait_node, duration_seconds) ## Gets the default animation length for joining characters @@ -360,7 +384,7 @@ func add_character(character:DialogicCharacter, portrait:String, position_idx:i ## Changes the portrait of a character. Only works with joined characters. -func change_character_portrait(character:DialogicCharacter, portrait:String, update_transform:=true) -> void: +func change_character_portrait(character: DialogicCharacter, portrait: String, update_transform := true) -> void: if !is_character_joined(character): return @@ -403,8 +427,8 @@ func change_character_extradata(character:DialogicCharacter, extra_data:="") -> ## Starts the given animation on the given character. Only works with joined characters -func animate_character(character:DialogicCharacter, animation_path:String, length:float, repeats := 1) -> DialogicAnimation: - if !is_character_joined(character): +func animate_character(character: DialogicCharacter, animation_path: String, length: float, repeats := 1) -> DialogicAnimation: + if not is_character_joined(character): return null animation_path = DialogicResourceUtil.guess_special_resource("PortraitAnimation", animation_path, "") @@ -413,16 +437,21 @@ func animate_character(character:DialogicCharacter, animation_path:String, lengt ## Moves the given character to the given position. Only works with joined characters -func move_character(character:DialogicCharacter, position_idx:int, time:= 0.0) -> void: +func move_character(character: DialogicCharacter, position_idx: int, time := 0.0) -> void: if !is_character_joined(character): return if dialogic.current_state_info.portraits[character.resource_path].position_index == position_idx: return - for portrait_position in get_tree().get_nodes_in_group('dialogic_portrait_con_position'): + var containers: Array[Node] = get_tree().get_nodes_in_group('dialogic_portrait_con_position') + + for portrait_position: DialogicNode_PortraitContainer in containers: + if portrait_position.is_visible_in_tree() and portrait_position.position_index == position_idx: - _move_portrait(dialogic.current_state_info.portraits[character.resource_path].node, portrait_position, time) + var character_node: Node2D = dialogic.current_state_info.portraits[character.resource_path].node + + _move_portrait(character_node, portrait_position, time) dialogic.current_state_info.portraits[character.resource_path].position_index = position_idx character_moved.emit({'character':character, 'position_index':position_idx, 'time':time}) return @@ -578,39 +607,47 @@ func reset_portrait_position(position_index:int, time:= 0.0) -> void: #################################################################################################### ## Updates all portrait containers set to SPEAKER. -func change_speaker(speaker:DialogicCharacter = null, portrait:= ""): - for con in get_tree().get_nodes_in_group('dialogic_portrait_con_speaker'): - for character_node in con.get_children(): - if character_node.get_meta('character') != speaker: - _remove_portrait(character_node) +func change_speaker(speaker: DialogicCharacter = null, portrait := ""): + for container: Node in get_tree().get_nodes_in_group('dialogic_portrait_con_speaker'): + + for character_node: Node in container.get_children(): + + if not character_node.get_meta('character') == speaker: + + for portrait_node: Node in character_node.get_children(): + _remove_portrait(portrait_node) if speaker == null or speaker.portraits.is_empty(): continue - if con.get_children().is_empty(): - _create_character_node(speaker, con) + if container.get_children().is_empty(): + _create_character_node(speaker, container) elif portrait.is_empty(): continue if portrait.is_empty(): portrait = speaker.default_portrait - if con.portrait_prefix+portrait in speaker.portraits: - _change_portrait(con.get_child(0), con.portrait_prefix+portrait) + if container.portrait_prefix+portrait in speaker.portraits: + _change_portrait(container.get_child(0), container.portrait_prefix+portrait) else: - _change_portrait(con.get_child(0), portrait) + _change_portrait(container.get_child(0), portrait) # if the character has no portraits _change_portrait won't actually add a child node - if con.get_child(0).get_child_count() == 0: + if container.get_child(0).get_child_count() == 0: continue - _change_portrait_mirror(con.get_child(0)) + _change_portrait_mirror(container.get_child(0)) if speaker: + if speaker.resource_path != dialogic.current_state_info['speaker']: + if dialogic.current_state_info['speaker'] and is_character_joined(load(dialogic.current_state_info['speaker'])): dialogic.current_state_info['portraits'][dialogic.current_state_info['speaker']].node.get_child(0)._unhighlight() + if speaker and is_character_joined(speaker): dialogic.current_state_info['portraits'][speaker.resource_path].node.get_child(0)._highlight() + elif dialogic.current_state_info['speaker'] and is_character_joined(load(dialogic.current_state_info['speaker'])): dialogic.current_state_info['portraits'][dialogic.current_state_info['speaker']].node.get_child(0)._unhighlight() From e743897d95c487e2626faf4de19fd3114a08ecf2 Mon Sep 17 00:00:00 2001 From: Cake Date: Sat, 9 Mar 2024 05:03:28 +0100 Subject: [PATCH 22/55] Update a few portrait animations. --- .../Character/DefaultAnimations/fade_in.gd | 15 +++++++++++++++ .../Character/DefaultAnimations/fade_in_up.gd | 11 +++++++---- .../Character/DefaultAnimations/fade_out.gd | 15 +++++++++++++++ .../Character/DefaultAnimations/fade_out_down.gd | 8 ++++---- 4 files changed, 41 insertions(+), 8 deletions(-) create mode 100644 addons/dialogic/Modules/Character/DefaultAnimations/fade_in.gd create mode 100644 addons/dialogic/Modules/Character/DefaultAnimations/fade_out.gd diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/fade_in.gd b/addons/dialogic/Modules/Character/DefaultAnimations/fade_in.gd new file mode 100644 index 000000000..4c7aea9d4 --- /dev/null +++ b/addons/dialogic/Modules/Character/DefaultAnimations/fade_in.gd @@ -0,0 +1,15 @@ +extends DialogicAnimation + +func animate() -> void: + var tween := (node.create_tween() as Tween) + + node.modulate.a = 0 + tween.set_ease(Tween.EASE_OUT) + tween.set_trans(Tween.TRANS_SINE) + tween.set_parallel() + + tween.tween_property(node, 'modulate:a', 1.0, time) + + await tween.finished + finished_once.emit() + diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/fade_in_up.gd b/addons/dialogic/Modules/Character/DefaultAnimations/fade_in_up.gd index 4ee09eca2..bbe21b2de 100644 --- a/addons/dialogic/Modules/Character/DefaultAnimations/fade_in_up.gd +++ b/addons/dialogic/Modules/Character/DefaultAnimations/fade_in_up.gd @@ -1,14 +1,17 @@ extends DialogicAnimation -func animate(): +func animate() -> void: var tween := (node.create_tween() as Tween) + node.position.y = orig_pos.y + node.get_viewport().size.y/5 node.modulate.a = 0 tween.set_ease(Tween.EASE_OUT) tween.set_trans(Tween.TRANS_SINE) tween.set_parallel() - + tween.tween_property(node, 'position', orig_pos, time) tween.tween_property(node, 'modulate:a', 1.0, time) - - tween.finished.connect(emit_signal.bind('finished_once')) + + await tween.finished + finished_once.emit() + diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/fade_out.gd b/addons/dialogic/Modules/Character/DefaultAnimations/fade_out.gd new file mode 100644 index 000000000..17be7aafd --- /dev/null +++ b/addons/dialogic/Modules/Character/DefaultAnimations/fade_out.gd @@ -0,0 +1,15 @@ +extends DialogicAnimation + +func animate() -> void: + var tween := (node.create_tween() as Tween) + + node.modulate.a = 1.0 + tween.set_ease(Tween.EASE_OUT) + tween.set_trans(Tween.TRANS_SINE) + tween.set_parallel() + + tween.tween_property(node, "modulate:a", 0.0, time) + + await tween.finished + finished_once.emit() + diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/fade_out_down.gd b/addons/dialogic/Modules/Character/DefaultAnimations/fade_out_down.gd index 45adeef1f..e0d8c4fa2 100644 --- a/addons/dialogic/Modules/Character/DefaultAnimations/fade_out_down.gd +++ b/addons/dialogic/Modules/Character/DefaultAnimations/fade_out_down.gd @@ -1,12 +1,12 @@ extends DialogicAnimation -func animate(): +func animate() -> void: var tween := (node.create_tween() as Tween) tween.set_ease(Tween.EASE_IN_OUT) tween.set_trans(Tween.TRANS_SINE) tween.set_parallel() - + tween.tween_property(node, 'position:y', orig_pos.y + node.get_viewport().size.y/5, time) tween.tween_property(node, 'modulate:a', 0.0, time) - - tween.finished.connect(emit_signal.bind('finished_once')) + + tween.finished.connect(emit_signal.bind(finished_once)) From 5f2195b595a89490508e130a80cb0f2ae50f5641 Mon Sep 17 00:00:00 2001 From: Cake Date: Sun, 10 Mar 2024 09:39:35 +0100 Subject: [PATCH 23/55] Add modulation property method to `DialogicAnimation`. --- .../Modules/Character/class_dialogic_animation.gd | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/addons/dialogic/Modules/Character/class_dialogic_animation.gd b/addons/dialogic/Modules/Character/class_dialogic_animation.gd index 995849efd..271c2a18e 100644 --- a/addons/dialogic/Modules/Character/class_dialogic_animation.gd +++ b/addons/dialogic/Modules/Character/class_dialogic_animation.gd @@ -47,3 +47,16 @@ func pause() -> void: func resume() -> void: if node: node.process_mode = Node.PROCESS_MODE_INHERIT + + +## If the animation wants to change the modulation, this method +## will return the property to change. +## +## The [class CanvasGroup] can use `self_modulate` instead of `modulate` +## to uniformly change the modulation of all children without additively +## overlaying the modulations. +func get_modulation_property() -> String: + if node is CanvasGroup: + return "self_modulate" + else: + return "modulate" From 6bf699ccb1227e880a28a6fb0c52b9ec7d113ddf Mon Sep 17 00:00:00 2001 From: Cake Date: Sun, 10 Mar 2024 09:39:51 +0100 Subject: [PATCH 24/55] Add option to reverse animation. --- .../Modules/Character/class_dialogic_animation.gd | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/addons/dialogic/Modules/Character/class_dialogic_animation.gd b/addons/dialogic/Modules/Character/class_dialogic_animation.gd index 271c2a18e..713ba43bf 100644 --- a/addons/dialogic/Modules/Character/class_dialogic_animation.gd +++ b/addons/dialogic/Modules/Character/class_dialogic_animation.gd @@ -8,16 +8,23 @@ signal finished ## Set at runtime, will be the node to animate. var node: Node + ## Set at runtime, will be the length of the animation. var time: float + ## Set at runtime, will be the position at which to end the animation. var end_position: Vector2 + ## Set at runtime. The position the node started at. var orig_pos: Vector2 ## Used to repeate the animation for a number of times. var repeats: int +## If `true`, the animation will be reversed. +## This must be implemented by each animation or it will have no effect. +var is_reversed: bool = false + func _ready() -> void: finished_once.connect(finished_one_loop) @@ -29,6 +36,8 @@ func animate() -> void: pass +## This method controls whether to repeat the animation or not. +## Animations must call this once they finished an animation. func finished_one_loop() -> void: repeats -= 1 From 6e202f4ff2a14a74f779af802d7fcdc0a6884478 Mon Sep 17 00:00:00 2001 From: Cake Date: Sun, 10 Mar 2024 09:40:51 +0100 Subject: [PATCH 25/55] Add `portrait_animating` signal. --- addons/dialogic/Modules/Character/subsystem_portraits.gd | 3 +++ 1 file changed, 3 insertions(+) diff --git a/addons/dialogic/Modules/Character/subsystem_portraits.gd b/addons/dialogic/Modules/Character/subsystem_portraits.gd index 3974a77fd..c67f7df47 100644 --- a/addons/dialogic/Modules/Character/subsystem_portraits.gd +++ b/addons/dialogic/Modules/Character/subsystem_portraits.gd @@ -8,6 +8,9 @@ signal character_portrait_changed(info:Dictionary) signal character_moved(info:Dictionary) signal position_changed(info:Dictionary) +## Emitted when a portrait starts animating. +signal portrait_animating(character_node: Node, portrait_node: Node, animation_name: String, animation_length: float) + ## The default portrait scene. var default_portrait_scene: PackedScene = load(get_script().resource_path.get_base_dir().path_join('default_portrait.tscn')) From 5b54a43db6ea02f250ceb9b4a471cf509861405e Mon Sep 17 00:00:00 2001 From: Cake Date: Sun, 10 Mar 2024 09:41:24 +0100 Subject: [PATCH 26/55] Handle `portrait_animating`. --- .../Modules/Character/subsystem_portraits.gd | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/addons/dialogic/Modules/Character/subsystem_portraits.gd b/addons/dialogic/Modules/Character/subsystem_portraits.gd index c67f7df47..97d51922f 100644 --- a/addons/dialogic/Modules/Character/subsystem_portraits.gd +++ b/addons/dialogic/Modules/Character/subsystem_portraits.gd @@ -59,6 +59,20 @@ func _ready() -> void: if !ProjectSettings.get_setting('dialogic/portraits/default_portrait', '').is_empty(): default_portrait_scene = load(ProjectSettings.get_setting('dialogic/portraits/default_portrait', '')) + portrait_animating.connect(_on_portrait_animating) + + +func _on_portrait_animating(character_node: Node, _portrait_node: Node, animation_name: String, animation_length: float) -> void: + var child_count := character_node.get_child_count() + + # Start removal of the current portrait. + if child_count > 1: + # We always delete the previous portrait node. + var previous_portrait_index := child_count - 2 + var previous_portrait_node := character_node.get_child(previous_portrait_index) + + _remove_portrait_timed(previous_portrait_node, animation_name, animation_length) + #endregion From 03f0b73f98ac17647701c9f70915df5a5ade9c0b Mon Sep 17 00:00:00 2001 From: Cake Date: Sun, 10 Mar 2024 09:43:44 +0100 Subject: [PATCH 27/55] Add `is_silent` and `is_reversed` to `_animate_portrait`. --- .../Modules/Character/subsystem_portraits.gd | 67 +++++++++++-------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/addons/dialogic/Modules/Character/subsystem_portraits.gd b/addons/dialogic/Modules/Character/subsystem_portraits.gd index 97d51922f..49e40b14d 100644 --- a/addons/dialogic/Modules/Character/subsystem_portraits.gd +++ b/addons/dialogic/Modules/Character/subsystem_portraits.gd @@ -103,13 +103,12 @@ func _create_character_node(character:DialogicCharacter, container:DialogicNode_ ## This allows for cross-fade effects and other animations. ## ## If [param duration_seconds] is `0.0`, the portrait will be removed instantly. -func _remove_portrait_timed(portrait_node: Node, duration_seconds: float) -> void: - +func _remove_portrait_timed(portrait_node: Node, animation_path := "Fade Out", duration_seconds := 0.0) -> void: if duration_seconds > 0: # TODO: Allow setting the animation - var animation_name := DialogicResourceUtil.guess_special_resource("PortraitAnimation", "Fade Out", "") - var animation := _animate_portrait(portrait_node, animation_name, duration_seconds) + var animation_name := DialogicResourceUtil.guess_special_resource("PortraitAnimation", animation_path, "") + var animation := _animate_portrait(portrait_node, animation_name, duration_seconds, 0, true, true) await animation.finished portrait_node.queue_free() @@ -141,10 +140,6 @@ func _change_portrait(character_node: Node2D, portrait: String, update_transform info['same_scene'] = true else: - # Start removal of the current portrait. - if character_node.get_child_count() > 0: - _remove_portrait(character_node.get_child(0), 2.0) - if ResourceLoader.exists(scene_path): var packed_scene: PackedScene = load(scene_path) if packed_scene: @@ -202,50 +197,62 @@ func _update_portrait_transform(portrait_node: Node, time:float = 0.0) -> void: (character.scale * portrait_info.get('scale', 1))*int(apply_character_scale)+portrait_info.get('scale', 1)*int(!apply_character_scale)) var tween: Tween + if character_node.has_meta('move_tween'): + if character_node.get_meta('move_tween').is_running(): time = character_node.get_meta('move_time')-character_node.get_meta('move_tween').get_total_elapsed_time() tween = character_node.get_meta('move_tween') + if time == 0: character_node.position = transform.position portrait_node.position = character.offset + portrait_info.get('offset', Vector2()) portrait_node.scale = transform.size else: + if !tween: tween = character_node.create_tween().set_parallel().set_ease(Tween.EASE_IN_OUT).set_trans(Tween.TRANS_SINE) character_node.set_meta('move_tween', tween) character_node.set_meta('move_time', time) + tween.tween_property(character_node, 'position', transform.position, time) tween.tween_property(portrait_node, 'position',character.offset + portrait_info.get('offset', Vector2()), time) tween.tween_property(portrait_node, 'scale', transform.size, time) ## Animates the portrait in the given container with the given animation. -func _animate_portrait(character_node: Node2D, animation_path: String, length: float, repeats := 1) -> DialogicAnimation: - if character_node.has_meta('animation_node') and is_instance_valid(character_node.get_meta('animation_node')): - character_node.get_meta('animation_node').queue_free() +func _animate_portrait(portrait_node: Node, animation_path: String, length: float, repeats := 1, is_reversed := false, is_silent := false) -> DialogicAnimation: + + if portrait_node.has_meta('animation_node') and is_instance_valid(portrait_node.get_meta('animation_node')): + portrait_node.get_meta('animation_node').queue_free() - # Animate the portrait node. - character_node = character_node.get_child(-1) var anim_script: Script = load(animation_path) var anim_node := Node.new() anim_node.set_script(anim_script) anim_node = (anim_node as DialogicAnimation) - anim_node.node = character_node - anim_node.orig_pos = character_node.position - anim_node.end_position = character_node.position + anim_node.node = portrait_node + anim_node.orig_pos = portrait_node.position + anim_node.end_position = portrait_node.position anim_node.time = length anim_node.repeats = repeats + anim_node.is_reversed = is_reversed + add_child(anim_node) anim_node.animate() - character_node.set_meta('animation_node', anim_node) + + portrait_node.set_meta("animation_path", animation_path) + portrait_node.set_meta("animation_length", length) + portrait_node.set_meta("animation_node", anim_node) + + if not is_silent: + portrait_animating.emit(portrait_node.get_parent(), portrait_node, animation_path, length) return anim_node ## Moves the given portrait to the given container. -func _move_portrait(portrait_node: Node2D, portrait_container:DialogicNode_PortraitContainer, time:= 0.0) -> void: +func _move_portrait(portrait_node: Node2D, portrait_container: DialogicNode_PortraitContainer, time := 0.0) -> void: var global_pos := portrait_node.global_position if portrait_node.get_parent(): @@ -259,7 +266,7 @@ func _move_portrait(portrait_node: Node2D, portrait_container:DialogicNode_Portr ## Changes the given portraits z_index. -func _change_portrait_z_index(character_node:Node2D, z_index:int, update_zindex:= true) -> void: +func _change_portrait_z_index(character_node: Node, z_index:int, update_zindex:= true) -> void: if update_zindex: character_node.get_parent().set_meta('z_index', z_index) @@ -271,18 +278,16 @@ func _change_portrait_z_index(character_node:Node2D, z_index:int, update_zindex: idx += 1 -func z_sort_portrait_containers(con1:DialogicNode_PortraitContainer, con2:DialogicNode_PortraitContainer) -> bool: +func z_sort_portrait_containers(con1: DialogicNode_PortraitContainer, con2: DialogicNode_PortraitContainer) -> bool: if con1.get_meta('z_index', 0) < con2.get_meta('z_index', 0): return true + return false ## Private method to remove a [param portrait_node]. -## -## Accepts optional [duration_seconds] to remove the portrait over a span -## of time. -func _remove_portrait(portrait_node: Node2D, duration_seconds := 1.0) -> void: - _remove_portrait_timed(portrait_node, duration_seconds) +func _remove_portrait(portrait_node: Node) -> void: + _remove_portrait_timed(portrait_node) ## Gets the default animation length for joining characters @@ -353,7 +358,7 @@ func join_character(character:DialogicCharacter, portrait:String, position_idx: animation_name = DialogicResourceUtil.guess_special_resource("PortraitAnimation", animation_name, "") if animation_name and animation_length > 0: - var anim: DialogicAnimation = _animate_portrait(character_node, animation_name, animation_length) + var anim: DialogicAnimation = _animate_portrait(character_node.get_child(-1), animation_name, animation_length) if animation_wait: dialogic.current_state = DialogicGameHandler.States.ANIMATING @@ -430,6 +435,7 @@ func change_character_mirror(character:DialogicCharacter, mirrored:= false, forc func change_character_z_index(character:DialogicCharacter, z_index:int, update_zindex:= true) -> void: if !is_character_joined(character): return + _change_portrait_z_index(dialogic.current_state_info.portraits[character.resource_path].node, z_index, update_zindex) if update_zindex: dialogic.current_state_info.portraits[character.resource_path]['z_index'] = z_index @@ -450,7 +456,10 @@ func animate_character(character: DialogicCharacter, animation_path: String, len animation_path = DialogicResourceUtil.guess_special_resource("PortraitAnimation", animation_path, "") - return _animate_portrait(dialogic.current_state_info.portraits[character.resource_path].node, animation_path, length, repeats) + var character_node: Node = dialogic.current_state_info.portraits[character.resource_path].node + var portrait_node: Node = character_node.get_child(-1) + + return _animate_portrait(portrait_node, animation_path, length, repeats) ## Moves the given character to the given position. Only works with joined characters @@ -489,7 +498,7 @@ func leave_character(character:DialogicCharacter, animation_name:= "", animation animation_name = DialogicResourceUtil.guess_special_resource("PortraitAnimation", animation_name, "") - if !animation_name.is_empty(): + if not animation_name.is_empty(): var anim := animate_character(character, animation_name, animation_length) anim.finished.connect(remove_character.bind(character)) @@ -624,7 +633,7 @@ func reset_portrait_position(position_index:int, time:= 0.0) -> void: #################################################################################################### ## Updates all portrait containers set to SPEAKER. -func change_speaker(speaker: DialogicCharacter = null, portrait := ""): +func change_speaker(speaker: DialogicCharacter = null, portrait := "") -> void: for container: Node in get_tree().get_nodes_in_group('dialogic_portrait_con_speaker'): for character_node: Node in container.get_children(): From a1de211915e091b7dac1ce41759689cc7f6d2634 Mon Sep 17 00:00:00 2001 From: Cake Date: Sun, 10 Mar 2024 10:07:00 +0100 Subject: [PATCH 28/55] Fix wording. --- addons/dialogic/Modules/Character/event_character.gd | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/addons/dialogic/Modules/Character/event_character.gd b/addons/dialogic/Modules/Character/event_character.gd index 63584948c..553315a27 100644 --- a/addons/dialogic/Modules/Character/event_character.gd +++ b/addons/dialogic/Modules/Character/event_character.gd @@ -145,10 +145,10 @@ func _execute() -> void: if dialogic.Inputs.auto_skip.enabled: var time_per_event: float = dialogic.Inputs.auto_skip.time_per_event - var time_for_repitions: float = time_per_event / animation_repeats - final_animation_length = time_for_repitions + var time_for_repetitions: float = time_per_event / animation_repeats + final_animation_length = time_for_repetitions - var anim: DialogicAnimation = dialogic.Portraits.animate_character( + var animation := dialogic.Portraits.animate_character( character, animation_name, final_animation_length, @@ -157,7 +157,7 @@ func _execute() -> void: if animation_wait: dialogic.current_state = DialogicGameHandler.States.ANIMATING - await anim.finished + await animation.finished dialogic.current_state = DialogicGameHandler.States.IDLE finish() From 4c62f9b2e1662762af794a24b9b77d9bd332e631 Mon Sep 17 00:00:00 2001 From: Cake Date: Sun, 10 Mar 2024 10:09:40 +0100 Subject: [PATCH 29/55] Use the reversed `Fade In` character animation. --- .../Character/DefaultAnimations/fade_out.gd | 15 --------------- .../Modules/Character/subsystem_portraits.gd | 2 +- 2 files changed, 1 insertion(+), 16 deletions(-) delete mode 100644 addons/dialogic/Modules/Character/DefaultAnimations/fade_out.gd diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/fade_out.gd b/addons/dialogic/Modules/Character/DefaultAnimations/fade_out.gd deleted file mode 100644 index 17be7aafd..000000000 --- a/addons/dialogic/Modules/Character/DefaultAnimations/fade_out.gd +++ /dev/null @@ -1,15 +0,0 @@ -extends DialogicAnimation - -func animate() -> void: - var tween := (node.create_tween() as Tween) - - node.modulate.a = 1.0 - tween.set_ease(Tween.EASE_OUT) - tween.set_trans(Tween.TRANS_SINE) - tween.set_parallel() - - tween.tween_property(node, "modulate:a", 0.0, time) - - await tween.finished - finished_once.emit() - diff --git a/addons/dialogic/Modules/Character/subsystem_portraits.gd b/addons/dialogic/Modules/Character/subsystem_portraits.gd index 49e40b14d..b298675d7 100644 --- a/addons/dialogic/Modules/Character/subsystem_portraits.gd +++ b/addons/dialogic/Modules/Character/subsystem_portraits.gd @@ -103,7 +103,7 @@ func _create_character_node(character:DialogicCharacter, container:DialogicNode_ ## This allows for cross-fade effects and other animations. ## ## If [param duration_seconds] is `0.0`, the portrait will be removed instantly. -func _remove_portrait_timed(portrait_node: Node, animation_path := "Fade Out", duration_seconds := 0.0) -> void: +func _remove_portrait_timed(portrait_node: Node, animation_path := "Fade In", duration_seconds := 0.0) -> void: if duration_seconds > 0: # TODO: Allow setting the animation From 7ba1ce9c98a0acd4cc20b447d077c3c7ab72247a Mon Sep 17 00:00:00 2001 From: Cake Date: Sun, 10 Mar 2024 10:15:59 +0100 Subject: [PATCH 30/55] Implement `is_reversed` for `Fade In` and `Fade In Up`. --- .../Character/DefaultAnimations/fade_in.gd | 17 ++++++++--- .../Character/DefaultAnimations/fade_in_up.gd | 28 ++++++++++++++++--- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/fade_in.gd b/addons/dialogic/Modules/Character/DefaultAnimations/fade_in.gd index 4c7aea9d4..5a8bb0d4d 100644 --- a/addons/dialogic/Modules/Character/DefaultAnimations/fade_in.gd +++ b/addons/dialogic/Modules/Character/DefaultAnimations/fade_in.gd @@ -3,12 +3,21 @@ extends DialogicAnimation func animate() -> void: var tween := (node.create_tween() as Tween) - node.modulate.a = 0 + var start := 0.0 + var end := 1.0 + + if is_reversed: + start = 1.0 + end = 0.0 + + var property := get_modulation_property() + var original_color: Color = node.get(property) + original_color.a = start + node.set(property, original_color) + tween.set_ease(Tween.EASE_OUT) tween.set_trans(Tween.TRANS_SINE) - tween.set_parallel() - - tween.tween_property(node, 'modulate:a', 1.0, time) + tween.tween_property(node, property + ":a", end, time) await tween.finished finished_once.emit() diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/fade_in_up.gd b/addons/dialogic/Modules/Character/DefaultAnimations/fade_in_up.gd index bbe21b2de..a64b299b3 100644 --- a/addons/dialogic/Modules/Character/DefaultAnimations/fade_in_up.gd +++ b/addons/dialogic/Modules/Character/DefaultAnimations/fade_in_up.gd @@ -3,14 +3,34 @@ extends DialogicAnimation func animate() -> void: var tween := (node.create_tween() as Tween) - node.position.y = orig_pos.y + node.get_viewport().size.y/5 - node.modulate.a = 0 + var start_pos: Vector2 = orig_pos.y + node.get_viewport().size.y / 5 + var end_pos := orig_pos + + var start_modulation := 0.0 + var end_modulation := 1.0 + + if is_reversed: + end_pos = start_pos + start_pos = orig_pos + end_modulation = 0.0 + start_modulation = 1.0 + + node.position.y = start_pos + tween.set_ease(Tween.EASE_OUT) tween.set_trans(Tween.TRANS_SINE) tween.set_parallel() - tween.tween_property(node, 'position', orig_pos, time) - tween.tween_property(node, 'modulate:a', 1.0, time) + tween.tween_property(node, "position", end_pos, time) + + var property := get_modulation_property() + + var original_modulation: Color = node.get(property) + original_modulation.a = start_modulation + node.set(property, original_modulation) + var modulation_alpha := property + ":a" + + tween.tween_property(node, modulation_alpha, end_modulation, time) await tween.finished finished_once.emit() From d0a2fbc50908966675b7b6098ade323b6ef64df8 Mon Sep 17 00:00:00 2001 From: Cake Date: Sun, 10 Mar 2024 11:14:11 +0100 Subject: [PATCH 31/55] Fix incorrect typing. --- .../Character/DefaultAnimations/fade_in_up.gd | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/fade_in_up.gd b/addons/dialogic/Modules/Character/DefaultAnimations/fade_in_up.gd index a64b299b3..2ed6c6444 100644 --- a/addons/dialogic/Modules/Character/DefaultAnimations/fade_in_up.gd +++ b/addons/dialogic/Modules/Character/DefaultAnimations/fade_in_up.gd @@ -3,25 +3,26 @@ extends DialogicAnimation func animate() -> void: var tween := (node.create_tween() as Tween) - var start_pos: Vector2 = orig_pos.y + node.get_viewport().size.y / 5 - var end_pos := orig_pos + var start_height: float = orig_pos.y + node.get_viewport().size.y / 5 + var end_height := orig_pos.y var start_modulation := 0.0 var end_modulation := 1.0 if is_reversed: - end_pos = start_pos - start_pos = orig_pos + end_height = start_height + start_height = orig_pos.y end_modulation = 0.0 start_modulation = 1.0 - node.position.y = start_pos + node.position.y = start_height tween.set_ease(Tween.EASE_OUT) tween.set_trans(Tween.TRANS_SINE) tween.set_parallel() - tween.tween_property(node, "position", end_pos, time) + var end_postion := Vector2(orig_pos.x, end_height) + tween.tween_property(node, "position", end_postion, time) var property := get_modulation_property() From c6fb6c3f6e2f2cb8f307633468f6760399610ce6 Mon Sep 17 00:00:00 2001 From: Cake Date: Sun, 10 Mar 2024 12:53:38 +0100 Subject: [PATCH 32/55] Set `z_index` higher to let removing portraits appear above. --- addons/dialogic/Modules/Character/subsystem_portraits.gd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/dialogic/Modules/Character/subsystem_portraits.gd b/addons/dialogic/Modules/Character/subsystem_portraits.gd index b298675d7..f09133e01 100644 --- a/addons/dialogic/Modules/Character/subsystem_portraits.gd +++ b/addons/dialogic/Modules/Character/subsystem_portraits.gd @@ -70,7 +70,7 @@ func _on_portrait_animating(character_node: Node, _portrait_node: Node, animatio # We always delete the previous portrait node. var previous_portrait_index := child_count - 2 var previous_portrait_node := character_node.get_child(previous_portrait_index) - + previous_portrait_node.z_index = 2 _remove_portrait_timed(previous_portrait_node, animation_name, animation_length) #endregion From e79364094cf5a0bbaf735ca1b7616e19452d7bde Mon Sep 17 00:00:00 2001 From: Cake Date: Sun, 10 Mar 2024 12:54:19 +0100 Subject: [PATCH 33/55] Add default removal of old sprites if no animation was set. --- .../Modules/Character/event_character.gd | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/addons/dialogic/Modules/Character/event_character.gd b/addons/dialogic/Modules/Character/event_character.gd index 553315a27..516f884ea 100644 --- a/addons/dialogic/Modules/Character/event_character.gd +++ b/addons/dialogic/Modules/Character/event_character.gd @@ -115,7 +115,7 @@ func _execute() -> void: ) Actions.UPDATE: - if !character or !dialogic.Portraits.is_character_joined(character): + if character == null or not dialogic.Portraits.is_character_joined(character): finish() return @@ -160,9 +160,23 @@ func _execute() -> void: await animation.finished dialogic.current_state = DialogicGameHandler.States.IDLE + else: + _remove_portrait_default_animation(character) + + finish() +func _remove_portrait_default_animation(target_character: DialogicCharacter) -> void: + var character_node: Node = dialogic.current_state_info["portraits"][target_character.resource_path].node + var child_count := character_node.get_child_count() + + if child_count > 1: + var portrait_node := character_node.get_child(0) + portrait_node.z_index = 2 + # TODO: get default remove animation + dialogic.Portraits._remove_portrait_timed(portrait_node, "Fade In", 1.0) + ################################################################################ ## INITIALIZE ################################################################################ @@ -192,13 +206,18 @@ func to_text() -> String: var default_values := DialogicUtil.get_custom_event_defaults(event_name) if character or character_identifier == '--All--': + if action == Actions.LEAVE and character_identifier == '--All--': result_string += "--All--" + else: var name := DialogicResourceUtil.get_unique_identifier(character.resource_path) + if name.count(" ") > 0: name = '"' + name + '"' + result_string += name + if portrait.strip_edges() != default_values.get('portrait', '') and action != Actions.LEAVE and (action != Actions.UPDATE or set_portrait): result_string+= " ("+portrait+")" @@ -274,12 +293,12 @@ func from_text(string:String) -> void: var shortcode_params := parse_shortcode_parameters(result.get_string('shortcode')) animation_name = shortcode_params.get('animation', '') - var animLength = shortcode_params.get('length', '0.5').to_float() + var animLength: float = shortcode_params.get('length', '0.5').to_float() if typeof(animLength) == TYPE_FLOAT: animation_length = animLength else: - animation_length = animLength.to_float() + animation_length = animLength var wait_for_animation: String = shortcode_params.get('wait', 'false') animation_wait = DialogicUtil.str_to_bool(wait_for_animation) @@ -503,12 +522,16 @@ func _get_code_completion(CodeCompletionHelper:Node, TextNode:TextEdit, line:Str if '[' in line: if CodeCompletionHelper.get_line_untill_caret(line).ends_with('animation="'): var animations := [] + if line.begins_with('join'): animations = DialogicUtil.get_portrait_animation_scripts(DialogicUtil.AnimationType.IN) + if line.begins_with('update'): animations = DialogicUtil.get_portrait_animation_scripts(DialogicUtil.AnimationType.ACTION) + if line.begins_with('leave'): animations = DialogicUtil.get_portrait_animation_scripts(DialogicUtil.AnimationType.OUT) + for script in animations: TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, DialogicUtil.pretty_name(script), DialogicUtil.pretty_name(script)+'" ', TextNode.syntax_highlighter.normal_color) elif CodeCompletionHelper.get_line_untill_caret(line).ends_with('wait="') or CodeCompletionHelper.get_line_untill_caret(line).ends_with('mirrored="'): From ac51e2f16417702c3ebf473ffac7209a6e253368 Mon Sep 17 00:00:00 2001 From: Cake Date: Sun, 10 Mar 2024 15:34:17 +0100 Subject: [PATCH 34/55] Add Default Cross-Fade Settings to the Editor. --- .../Modules/Character/event_character.gd | 19 ++-- .../Modules/Character/settings_portraits.gd | 89 ++++++++++++++----- .../Modules/Character/settings_portraits.tscn | 57 ++++++++---- 3 files changed, 112 insertions(+), 53 deletions(-) diff --git a/addons/dialogic/Modules/Character/event_character.gd b/addons/dialogic/Modules/Character/event_character.gd index 516f884ea..184caeda8 100644 --- a/addons/dialogic/Modules/Character/event_character.gd +++ b/addons/dialogic/Modules/Character/event_character.gd @@ -139,6 +139,12 @@ func _execute() -> void: dialogic.Portraits.move_character(character, position, final_position_move_time) + if animation_name.is_empty(): + animation_name = ProjectSettings.get_setting("dialogic/animations/cross_fade_default", "Fade In") + animation_length = ProjectSettings.get_setting("dialogic/animations/cross_fade_default_length", 0.5) + animation_wait = ProjectSettings.get_setting("dialogic/animations/cross_fade_default_wait", false) + print(animation_name, animation_length, animation_wait) + if animation_name: var final_animation_length: float = animation_length var final_animation_repetitions: int = animation_repeats @@ -160,23 +166,10 @@ func _execute() -> void: await animation.finished dialogic.current_state = DialogicGameHandler.States.IDLE - else: - _remove_portrait_default_animation(character) - finish() -func _remove_portrait_default_animation(target_character: DialogicCharacter) -> void: - var character_node: Node = dialogic.current_state_info["portraits"][target_character.resource_path].node - var child_count := character_node.get_child_count() - - if child_count > 1: - var portrait_node := character_node.get_child(0) - portrait_node.z_index = 2 - # TODO: get default remove animation - dialogic.Portraits._remove_portrait_timed(portrait_node, "Fade In", 1.0) - ################################################################################ ## INITIALIZE ################################################################################ diff --git a/addons/dialogic/Modules/Character/settings_portraits.gd b/addons/dialogic/Modules/Character/settings_portraits.gd index a637d9f45..9ecb68403 100644 --- a/addons/dialogic/Modules/Character/settings_portraits.gd +++ b/addons/dialogic/Modules/Character/settings_portraits.gd @@ -2,41 +2,54 @@ extends DialogicSettingsPage -func _ready(): +func _ready() -> void: %JoinDefault.get_suggestions_func = get_join_animation_suggestions %JoinDefault.mode = 1 %LeaveDefault.get_suggestions_func = get_leave_animation_suggestions %LeaveDefault.mode = 1 + %CrossFadeDefault.get_suggestions_func = get_join_animation_suggestions + %CrossFadeDefault.mode = 1 + %CrossFadeDefaultLength.value_changed.connect(_on_CrossFadeDefaultLength_value_changed) + %CrossFadeDefaultWait.toggled.connect(_on_CrossFadeDefaultWait_toggled) + %CrossFadeDefault.value_changed.connect(_on_CrossFadeDefault_value_changed) -func _refresh(): + +func _refresh() -> void: %CustomPortraitScene.resource_icon = get_theme_icon("PackedScene", "EditorIcons") %CustomPortraitScene.set_value(ProjectSettings.get_setting('dialogic/portraits/default_portrait', '')) - %JoinDefault.resource_icon = get_theme_icon("Animation", "EditorIcons") %LeaveDefault.resource_icon = get_theme_icon("Animation", "EditorIcons") + %CrossFadeDefault.resource_icon = get_theme_icon("Animation", "EditorIcons") + %JoinDefault.set_value(DialogicUtil.pretty_name(ProjectSettings.get_setting('dialogic/animations/join_default', - get_script().resource_path.get_base_dir().path_join('DefaultAnimations/fade_in_up.gd')))) - %LeaveDefault.set_value(ProjectSettings.get_setting('dialogic/animations/leave_default', - get_script().resource_path.get_base_dir().path_join('DefaultAnimations/fade_out_down.gd'))) + get_script().resource_path.get_base_dir().path_join('DefaultAnimations/fade_in_up.gd')))) %JoinDefaultLength.set_value(ProjectSettings.get_setting('dialogic/animations/join_default_length', 0.5)) + %JoinDefaultWait.button_pressed = ProjectSettings.get_setting('dialogic/animations/join_default_wait', true) + + %LeaveDefault.set_value(ProjectSettings.get_setting('dialogic/animations/leave_default', + get_script().resource_path.get_base_dir().path_join('DefaultAnimations/fade_out_down.gd'))) %LeaveDefaultLength.set_value(ProjectSettings.get_setting('dialogic/animations/leave_default_length', 0.5)) %LeaveDefaultWait.button_pressed = ProjectSettings.get_setting('dialogic/animations/leave_default_wait', true) - %JoinDefaultWait.button_pressed = ProjectSettings.get_setting('dialogic/animations/join_default_wait', true) + %CrossFadeDefault.set_value(ProjectSettings.get_setting('dialogic/animations/cross_fade_default', + get_script().resource_path.get_base_dir().path_join('DefaultAnimations/fade_in.gd'))) + %CrossFadeDefaultLength.set_value(ProjectSettings.get_setting('dialogic/animations/cross_fade_default_length', 0.5)) + %CrossFadeDefaultWait.button_pressed = ProjectSettings.get_setting('dialogic/animations/cross_fade_default_wait', true) -func _on_custom_portrait_scene_value_changed(property_name:String, value:String) -> void: + +func _on_custom_portrait_scene_value_changed(_property_name: String, value: String) -> void: ProjectSettings.set_setting('dialogic/portraits/default_portrait', value) ProjectSettings.save() -func _on_LeaveDefault_value_changed(property_name:String, value:String) -> void: +func _on_LeaveDefault_value_changed(_property_name: String, value: String) -> void: ProjectSettings.set_setting('dialogic/animations/leave_default', value) ProjectSettings.save() -func _on_JoinDefault_value_changed(property_name:String, value:String) -> void: +func _on_JoinDefault_value_changed(_property_name: String, value: String) -> void: ProjectSettings.set_setting('dialogic/animations/join_default', value) ProjectSettings.save() @@ -50,31 +63,65 @@ func _on_LeaveDefaultLength_value_changed(value:float) -> void: ProjectSettings.set_setting('dialogic/animations/leave_default_length', value) ProjectSettings.save() + func _on_JoinDefaultWait_toggled(button_pressed:bool) -> void: ProjectSettings.set_setting('dialogic/animations/join_default_wait', button_pressed) ProjectSettings.save() + func _on_LeaveDefaultWait_toggled(button_pressed:bool) -> void: ProjectSettings.set_setting('dialogic/animations/leave_default_wait', button_pressed) ProjectSettings.save() -func get_join_animation_suggestions(search_text:String) -> Dictionary: - var suggestions = {} - for anim in list_animations(): - if '_in' in anim.get_file(): - suggestions[DialogicUtil.pretty_name(anim)] = {'value':anim, 'icon':get_theme_icon('Animation', 'EditorIcons')} +func _on_CrossFadeDefaultLength_value_changed(value: float) -> void: + ProjectSettings.set_setting('dialogic/animations/cross_fade_default_length', value) + ProjectSettings.save() + + +func _on_CrossFadeDefaultWait_toggled(button_pressed: bool) -> void: + ProjectSettings.set_setting('dialogic/animations/cross_fade_default_wait', button_pressed) + ProjectSettings.save() + + +func _on_CrossFadeDefault_value_changed(_property_name: String, value: String) -> void: + ProjectSettings.set_setting('dialogic/animations/cross_fade_default', value) + ProjectSettings.save() + + +func get_join_animation_suggestions(_search_text: String) -> Dictionary: + var suggestions := {} + + for animation: String in list_animations(): + + if '_in' in animation.get_file(): + suggestions[DialogicUtil.pretty_name(animation)] = { + 'value':animation, + 'icon':get_theme_icon('Animation', 'EditorIcons') + } + return suggestions -func get_leave_animation_suggestions(search_text:String) -> Dictionary: - var suggestions = {} - for anim in list_animations(): - if '_out' in anim.get_file(): - suggestions[DialogicUtil.pretty_name(anim)] = {'value':anim, 'icon':get_theme_icon('Animation', 'EditorIcons')} + +func get_leave_animation_suggestions(_search_text: String) -> Dictionary: + var suggestions := {} + + for animation: String in list_animations(): + + if '_out' in animation.get_file(): + suggestions[DialogicUtil.pretty_name(animation)] = { + 'value':animation, + 'icon':get_theme_icon('Animation', 'EditorIcons') + } + return suggestions + func list_animations() -> Array: - var list = DialogicUtil.listdir(get_script().resource_path.get_base_dir().path_join('DefaultAnimations'), true, false, true) + var defaultAnimationPath: String = get_script().resource_path.get_base_dir().path_join('DefaultAnimations') + var list: Array = DialogicUtil.listdir(defaultAnimationPath, true, false, true) + list.append_array(DialogicUtil.listdir(ProjectSettings.get_setting('dialogic/animations/custom_folder', 'res://addons/dialogic_additions/Animations'), true, false, true)) + return list diff --git a/addons/dialogic/Modules/Character/settings_portraits.tscn b/addons/dialogic/Modules/Character/settings_portraits.tscn index ad39f9174..ad5c2e156 100644 --- a/addons/dialogic/Modules/Character/settings_portraits.tscn +++ b/addons/dialogic/Modules/Character/settings_portraits.tscn @@ -1,22 +1,10 @@ -[gd_scene load_steps=7 format=3 uid="uid://cp463rpri5j8a"] +[gd_scene load_steps=5 format=3 uid="uid://cp463rpri5j8a"] [ext_resource type="Script" path="res://addons/dialogic/Modules/Character/settings_portraits.gd" id="2"] [ext_resource type="PackedScene" uid="uid://dbpkta2tjsqim" path="res://addons/dialogic/Editor/Common/hint_tooltip_icon.tscn" id="2_dce78"] [ext_resource type="PackedScene" uid="uid://dpwhshre1n4t6" path="res://addons/dialogic/Editor/Events/Fields/field_options_dynamic.tscn" id="3"] [ext_resource type="PackedScene" uid="uid://7mvxuaulctcq" path="res://addons/dialogic/Editor/Events/Fields/field_file.tscn" id="3_m06d8"] -[sub_resource type="Image" id="Image_8p738"] -data = { -"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0), -"format": "RGBA8", -"height": 16, -"mipmaps": false, -"width": 16 -} - -[sub_resource type="ImageTexture" id="ImageTexture_wre7v"] -image = SubResource("Image_8p738") - [node name="Portraits" type="VBoxContainer"] anchors_preset = 15 anchor_right = 1.0 @@ -37,7 +25,6 @@ text = "Default Portrait Scene [node name="HintTooltip" parent="Animations3" instance=ExtResource("2_dce78")] layout_mode = 2 tooltip_text = "If this is set, this scene will be what is used by default for any portrait that has no scene specified" -texture = SubResource("ImageTexture_wre7v") hint_text = "If this is set, this scene will be what is used by default for any portrait that has no scene specified" [node name="HBoxContainer" type="HBoxContainer" parent="."] @@ -66,15 +53,20 @@ text = "Default Animations [node name="HintTooltip" parent="Animations2" instance=ExtResource("2_dce78")] layout_mode = 2 -tooltip_text = "These settings are used for Leave and Join events if no animation is selected." -texture = SubResource("ImageTexture_wre7v") -hint_text = "These settings are used for Leave and Join events if no animation is selected." +tooltip_text = "These settings are used for Leave and Join events if no animation is selected. + +The Cross-Fade will play if the portrait of a character changes and +no animation is set." +hint_text = "These settings are used for Leave and Join events if no animation is selected. + +The Cross-Fade will play if the portrait of a character changes and +no animation is set." [node name="GridContainer" type="GridContainer" parent="."] layout_mode = 2 columns = 2 -[node name="Label3" type="Label" parent="GridContainer"] +[node name="DefaultJoinLabel" type="Label" parent="GridContainer"] layout_mode = 2 text = "Join" @@ -84,6 +76,7 @@ layout_mode = 2 [node name="JoinDefault" parent="GridContainer/DefaultIn" instance=ExtResource("3")] unique_name_in_owner = true layout_mode = 2 +mode = 1 [node name="JoinDefaultLength" type="SpinBox" parent="GridContainer/DefaultIn"] unique_name_in_owner = true @@ -95,7 +88,7 @@ unique_name_in_owner = true layout_mode = 2 text = "Wait:" -[node name="Label4" type="Label" parent="GridContainer"] +[node name="DefaultOutLabel" type="Label" parent="GridContainer"] layout_mode = 2 text = "Leave" @@ -105,6 +98,7 @@ layout_mode = 2 [node name="LeaveDefault" parent="GridContainer/DefaultOut" instance=ExtResource("3")] unique_name_in_owner = true layout_mode = 2 +mode = 1 [node name="LeaveDefaultLength" type="SpinBox" parent="GridContainer/DefaultOut"] unique_name_in_owner = true @@ -116,6 +110,28 @@ unique_name_in_owner = true layout_mode = 2 text = "Wait:" +[node name="CrossFadeLabel" type="Label" parent="GridContainer"] +layout_mode = 2 +text = "Cross-Fade" + +[node name="DefaultCrossFade" type="HBoxContainer" parent="GridContainer"] +layout_mode = 2 + +[node name="CrossFadeDefault" parent="GridContainer/DefaultCrossFade" instance=ExtResource("3")] +unique_name_in_owner = true +layout_mode = 2 +mode = 1 + +[node name="CrossFadeDefaultLength" type="SpinBox" parent="GridContainer/DefaultCrossFade"] +unique_name_in_owner = true +layout_mode = 2 +step = 0.1 + +[node name="CrossFadeDefaultWait" type="CheckButton" parent="GridContainer/DefaultCrossFade"] +unique_name_in_owner = true +layout_mode = 2 +text = "Wait:" + [connection signal="value_changed" from="HBoxContainer/CustomPortraitScene" to="." method="_on_custom_portrait_scene_value_changed"] [connection signal="value_changed" from="GridContainer/DefaultIn/JoinDefault" to="." method="_on_JoinDefault_value_changed"] [connection signal="value_changed" from="GridContainer/DefaultIn/JoinDefaultLength" to="." method="_on_JoinDefaultLength_value_changed"] @@ -123,3 +139,6 @@ text = "Wait:" [connection signal="value_changed" from="GridContainer/DefaultOut/LeaveDefault" to="." method="_on_LeaveDefault_value_changed"] [connection signal="value_changed" from="GridContainer/DefaultOut/LeaveDefaultLength" to="." method="_on_LeaveDefaultLength_value_changed"] [connection signal="toggled" from="GridContainer/DefaultOut/LeaveDefaultWait" to="." method="_on_LeaveDefaultWait_toggled"] +[connection signal="value_changed" from="GridContainer/DefaultCrossFade/CrossFadeDefault" to="." method="_on_LeaveDefault_value_changed"] +[connection signal="value_changed" from="GridContainer/DefaultCrossFade/CrossFadeDefaultLength" to="." method="_on_LeaveDefaultLength_value_changed"] +[connection signal="toggled" from="GridContainer/DefaultCrossFade/CrossFadeDefaultWait" to="." method="_on_LeaveDefaultWait_toggled"] From 5e628b16229f5472f72c34b16165f7d45e56cabd Mon Sep 17 00:00:00 2001 From: Cake Date: Mon, 11 Mar 2024 17:55:22 +0100 Subject: [PATCH 35/55] Add support for `_in_out` animations. --- addons/dialogic/Core/DialogicResourceUtil.gd | 6 ++- addons/dialogic/Core/DialogicUtil.gd | 39 +++++++++++++++---- .../{fade_in.gd => fade_in_out.gd} | 0 .../Modules/Character/event_character.gd | 3 +- 4 files changed, 38 insertions(+), 10 deletions(-) rename addons/dialogic/Modules/Character/DefaultAnimations/{fade_in.gd => fade_in_out.gd} (100%) diff --git a/addons/dialogic/Core/DialogicResourceUtil.gd b/addons/dialogic/Core/DialogicResourceUtil.gd index 710099972..3e74ab6ec 100644 --- a/addons/dialogic/Core/DialogicResourceUtil.gd +++ b/addons/dialogic/Core/DialogicResourceUtil.gd @@ -178,11 +178,15 @@ static func list_special_resources_of_type(type:String) -> Array: static func guess_special_resource(type:String, name:String, default:="") -> String: if special_resources.is_empty(): update_special_resources() + if name.begins_with('res://'): return name - for path in list_special_resources_of_type(type): + + for path: String in list_special_resources_of_type(type): + if DialogicUtil.pretty_name(path).to_lower() == name.to_lower(): return path + return default diff --git a/addons/dialogic/Core/DialogicUtil.gd b/addons/dialogic/Core/DialogicUtil.gd index 90f9c5a6f..81952ac85 100644 --- a/addons/dialogic/Core/DialogicUtil.gd +++ b/addons/dialogic/Core/DialogicUtil.gd @@ -102,21 +102,44 @@ static func get_indexers(include_custom := true, force_reload := false) -> Array enum AnimationType {ALL, IN, OUT, ACTION} -static func get_portrait_animation_scripts(type:=AnimationType.ALL, include_custom:=true) -> Array: + + + +static func get_portrait_animation_scripts(type := AnimationType.ALL, include_custom := true) -> Array: var animations := DialogicResourceUtil.list_special_resources_of_type("PortraitAnimation") + const CROSS_ANIMATION := "_in_out" + const OUT_ANIMATION := "_out" + const IN_ANIMATION := "_in" return animations.filter( - func(script): - if type == AnimationType.ALL: return true; - if type == AnimationType.IN: return '_in' in script; - if type == AnimationType.OUT: return '_out' in script; - if type == AnimationType.ACTION: return not ('_in' in script or '_out' in script)) + func(script: String) -> bool: + match (type): + AnimationType.ALL: + return true + + AnimationType.IN: + return IN_ANIMATION in script or CROSS_ANIMATION in script + AnimationType.OUT: + return OUT_ANIMATION in script or CROSS_ANIMATION in script -static func pretty_name(script:String) -> String: - var _name := script.get_file().trim_suffix("."+script.get_extension()) + # All animations that are not IN or OUT. + # Extra check for CROSS animations to prevent parsing parts + # of the name as an IN or OUT animation. + AnimationType.ACTION: + return CROSS_ANIMATION in script or not (IN_ANIMATION in script or OUT_ANIMATION in script) + + _: + return false + ) + + +## Turns a [param file_path] from `some_file.png` to `Some File`. +static func pretty_name(file_path: String) -> String: + var _name := file_path.get_file().trim_suffix("." + file_path.get_extension()) _name = _name.replace('_', ' ') _name = _name.capitalize() + return _name diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/fade_in.gd b/addons/dialogic/Modules/Character/DefaultAnimations/fade_in_out.gd similarity index 100% rename from addons/dialogic/Modules/Character/DefaultAnimations/fade_in.gd rename to addons/dialogic/Modules/Character/DefaultAnimations/fade_in_out.gd diff --git a/addons/dialogic/Modules/Character/event_character.gd b/addons/dialogic/Modules/Character/event_character.gd index 184caeda8..ec892b6a8 100644 --- a/addons/dialogic/Modules/Character/event_character.gd +++ b/addons/dialogic/Modules/Character/event_character.gd @@ -525,8 +525,9 @@ func _get_code_completion(CodeCompletionHelper:Node, TextNode:TextEdit, line:Str if line.begins_with('leave'): animations = DialogicUtil.get_portrait_animation_scripts(DialogicUtil.AnimationType.OUT) - for script in animations: + for script: String in animations: TextNode.add_code_completion_option(CodeEdit.KIND_MEMBER, DialogicUtil.pretty_name(script), DialogicUtil.pretty_name(script)+'" ', TextNode.syntax_highlighter.normal_color) + elif CodeCompletionHelper.get_line_untill_caret(line).ends_with('wait="') or CodeCompletionHelper.get_line_untill_caret(line).ends_with('mirrored="'): CodeCompletionHelper.suggest_bool(TextNode, TextNode.syntax_highlighter.normal_color) From 5f100933db3710f375d585eb530978ea7238e0f5 Mon Sep 17 00:00:00 2001 From: Cake Date: Tue, 12 Mar 2024 11:28:58 +0100 Subject: [PATCH 36/55] Remove characters with an animation. --- addons/dialogic/Core/DialogicResourceUtil.gd | 2 +- .../Modules/Character/subsystem_portraits.gd | 64 ++++++++++--------- 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/addons/dialogic/Core/DialogicResourceUtil.gd b/addons/dialogic/Core/DialogicResourceUtil.gd index 3e74ab6ec..95116f996 100644 --- a/addons/dialogic/Core/DialogicResourceUtil.gd +++ b/addons/dialogic/Core/DialogicResourceUtil.gd @@ -175,7 +175,7 @@ static func list_special_resources_of_type(type:String) -> Array: return special_resources.filter(func(x:Dictionary): return type == x.get('type','')).map(func(x:Dictionary): return x.get('path', '')) -static func guess_special_resource(type:String, name:String, default:="") -> String: +static func guess_special_resource(type: String, name: String, default := "") -> String: if special_resources.is_empty(): update_special_resources() diff --git a/addons/dialogic/Modules/Character/subsystem_portraits.gd b/addons/dialogic/Modules/Character/subsystem_portraits.gd index f09133e01..5402c2e34 100644 --- a/addons/dialogic/Modules/Character/subsystem_portraits.gd +++ b/addons/dialogic/Modules/Character/subsystem_portraits.gd @@ -103,7 +103,7 @@ func _create_character_node(character:DialogicCharacter, container:DialogicNode_ ## This allows for cross-fade effects and other animations. ## ## If [param duration_seconds] is `0.0`, the portrait will be removed instantly. -func _remove_portrait_timed(portrait_node: Node, animation_path := "Fade In", duration_seconds := 0.0) -> void: +func _remove_portrait_timed(portrait_node: Node, animation_path := "Fade In Out", duration_seconds := 0.0) -> void: if duration_seconds > 0: # TODO: Allow setting the animation @@ -226,7 +226,6 @@ func _animate_portrait(portrait_node: Node, animation_path: String, length: floa if portrait_node.has_meta('animation_node') and is_instance_valid(portrait_node.get_meta('animation_node')): portrait_node.get_meta('animation_node').queue_free() - var anim_script: Script = load(animation_path) var anim_node := Node.new() anim_node.set_script(anim_script) @@ -450,7 +449,7 @@ func change_character_extradata(character:DialogicCharacter, extra_data:="") -> ## Starts the given animation on the given character. Only works with joined characters -func animate_character(character: DialogicCharacter, animation_path: String, length: float, repeats := 1) -> DialogicAnimation: +func animate_character(character: DialogicCharacter, animation_path: String, length: float, repeats := 1, is_reversed := false) -> DialogicAnimation: if not is_character_joined(character): return null @@ -459,7 +458,7 @@ func animate_character(character: DialogicCharacter, animation_path: String, len var character_node: Node = dialogic.current_state_info.portraits[character.resource_path].node var portrait_node: Node = character_node.get_child(-1) - return _animate_portrait(portrait_node, animation_path, length, repeats) + return _animate_portrait(portrait_node, animation_path, length, repeats, is_reversed) ## Moves the given character to the given position. Only works with joined characters @@ -486,8 +485,8 @@ func move_character(character: DialogicCharacter, position_idx: int, time := 0.0 ## Removes a character with a given animation or the default animation. -func leave_character(character:DialogicCharacter, animation_name:= "", animation_length:= 0.0, animation_wait := false) -> void: - if !is_character_joined(character): +func leave_character(character: DialogicCharacter, animation_name:= "", animation_length:= 0.0, animation_wait := false) -> void: + if not is_character_joined(character): return if animation_name.is_empty(): @@ -498,17 +497,12 @@ func leave_character(character:DialogicCharacter, animation_name:= "", animation animation_name = DialogicResourceUtil.guess_special_resource("PortraitAnimation", animation_name, "") - if not animation_name.is_empty(): - var anim := animate_character(character, animation_name, animation_length) - anim.finished.connect(remove_character.bind(character)) + if not animation_name.is_empty(): + var portrait_node := get_character_portrait_node(character) + await _remove_portrait_timed(portrait_node.get_child(0), animation_name, animation_length) - if animation_wait: - dialogic.current_state = DialogicGameHandler.States.ANIMATING - await anim.finished - dialogic.current_state = DialogicGameHandler.States.IDLE - else: - remove_character(character) + remove_character(character) ## Removes all joined characters with a given animation or the default animation. @@ -526,32 +520,40 @@ func leave_all_characters(animation_name:="", animation_length:=0.0, animation_w dialogic.current_state = DialogicGameHandler.States.IDLE -## Removes the given characters portrait. Only works with joined characters -func remove_character(character:DialogicCharacter) -> void: - if !is_character_joined(character): - return - if is_instance_valid(dialogic.current_state_info['portraits'][character.resource_path].node) and \ - dialogic.current_state_info['portraits'][character.resource_path].node is Node: - _remove_portrait(dialogic.current_state_info['portraits'][character.resource_path].node) - character_left.emit({'character':character}) +func get_character_portrait_node(character: DialogicCharacter) -> Node: + if is_character_joined(character): + return dialogic.current_state_info['portraits'][character.resource_path].node + + return null + + +## Removes the given characters portrait. +## Only works with joined characters. +func remove_character(character: DialogicCharacter) -> void: + var character_node := get_character_portrait_node(character) + + if is_instance_valid(character_node) and character_node is Node: + character_node.queue_free() + character_left.emit({'character': character}) + dialogic.current_state_info['portraits'].erase(character.resource_path) ## Returns true if the given character is currently joined. -func is_character_joined(character:DialogicCharacter) -> bool: - if not character or !character.resource_path in dialogic.current_state_info['portraits']: +func is_character_joined(character: DialogicCharacter) -> bool: + if character == null or not character.resource_path in dialogic.current_state_info['portraits']: return false - if dialogic.current_state_info['portraits'][character.resource_path].get('node', null) != null and \ - is_instance_valid(dialogic.current_state_info['portraits'][character.resource_path].node): - return true - return false + + return true ## Returns a list of the joined charcters (as resources) func get_joined_characters() -> Array[DialogicCharacter]: - var chars: Array[DialogicCharacter]= [] - for char_path in dialogic.current_state_info.get('portraits', {}).keys(): + var chars: Array[DialogicCharacter] = [] + + for char_path: String in dialogic.current_state_info.get('portraits', {}).keys(): chars.append(load(char_path)) + return chars From 384b935a308adffeb06f35a1ef944ea764cfd5e8 Mon Sep 17 00:00:00 2001 From: Cake Date: Tue, 12 Mar 2024 15:02:20 +0100 Subject: [PATCH 37/55] Replace fallback animation. --- addons/dialogic/Modules/Character/event_character.gd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/dialogic/Modules/Character/event_character.gd b/addons/dialogic/Modules/Character/event_character.gd index ec892b6a8..c3d70d7a0 100644 --- a/addons/dialogic/Modules/Character/event_character.gd +++ b/addons/dialogic/Modules/Character/event_character.gd @@ -140,7 +140,7 @@ func _execute() -> void: dialogic.Portraits.move_character(character, position, final_position_move_time) if animation_name.is_empty(): - animation_name = ProjectSettings.get_setting("dialogic/animations/cross_fade_default", "Fade In") + animation_name = ProjectSettings.get_setting("dialogic/animations/cross_fade_default", "Fade In Out") animation_length = ProjectSettings.get_setting("dialogic/animations/cross_fade_default_length", 0.5) animation_wait = ProjectSettings.get_setting("dialogic/animations/cross_fade_default_wait", false) print(animation_name, animation_length, animation_wait) From 609a100298f867496372bb172af8f1b510208569 Mon Sep 17 00:00:00 2001 From: Cake Date: Tue, 12 Mar 2024 18:45:22 +0100 Subject: [PATCH 38/55] Fix awaiting animations before Dialogic can continue if characters leave or join. --- .../Modules/Character/subsystem_portraits.gd | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/addons/dialogic/Modules/Character/subsystem_portraits.gd b/addons/dialogic/Modules/Character/subsystem_portraits.gd index 5402c2e34..578500a1b 100644 --- a/addons/dialogic/Modules/Character/subsystem_portraits.gd +++ b/addons/dialogic/Modules/Character/subsystem_portraits.gd @@ -325,17 +325,22 @@ func _get_leave_default_length() -> float: func join_character(character:DialogicCharacter, portrait:String, position_idx:int, mirrored:= false, z_index:= 0, extra_data:= "", animation_name:= "", animation_length:= 0.0, animation_wait := false) -> Node: if is_character_joined(character): change_character_portrait(character, portrait) + if animation_name.is_empty(): animation_length = _get_join_default_length() + if animation_wait: dialogic.current_state = DialogicGameHandler.States.ANIMATING await get_tree().create_timer(animation_length).timeout dialogic.current_state = DialogicGameHandler.States.IDLE + move_character(character, position_idx, animation_length) change_character_mirror(character, mirrored) + return var character_node := add_character(character, portrait, position_idx) + if character_node == null: return null @@ -497,30 +502,34 @@ func leave_character(character: DialogicCharacter, animation_name:= "", animatio animation_name = DialogicResourceUtil.guess_special_resource("PortraitAnimation", animation_name, "") - if not animation_name.is_empty(): - var portrait_node := get_character_portrait_node(character) - await _remove_portrait_timed(portrait_node.get_child(0), animation_name, animation_length) + var character_node := get_character_node(character) + var last_portrait := character_node.get_child(-1) + + if animation_wait: + dialogic.current_state = DialogicGameHandler.States.ANIMATING + await _remove_portrait_timed(last_portrait, animation_name, animation_length) + dialogic.current_state = DialogicGameHandler.States.IDLE + + else: + await _remove_portrait_timed(last_portrait, animation_name, animation_length) remove_character(character) ## Removes all joined characters with a given animation or the default animation. -func leave_all_characters(animation_name:="", animation_length:=0.0, animation_wait:= false) -> void: +func leave_all_characters(animation_name:="", animation_length:=0.0, animation_wait := false) -> void: for character in get_joined_characters(): - leave_character(character, animation_name, animation_length, false) + await leave_character(character, animation_name, animation_length, animation_wait) if animation_name.is_empty(): animation_length = _get_leave_default_length() animation_wait = ProjectSettings.get_setting('dialogic/animations/leave_default_wait', true) - if animation_wait: - dialogic.current_state = DialogicGameHandler.States.ANIMATING - await get_tree().create_timer(animation_length).timeout - dialogic.current_state = DialogicGameHandler.States.IDLE - -func get_character_portrait_node(character: DialogicCharacter) -> Node: +## Finds the character node for a [param character]. +## Return `null` if the [param character] is not part of the scene. +func get_character_node(character: DialogicCharacter) -> Node: if is_character_joined(character): return dialogic.current_state_info['portraits'][character.resource_path].node @@ -530,7 +539,7 @@ func get_character_portrait_node(character: DialogicCharacter) -> Node: ## Removes the given characters portrait. ## Only works with joined characters. func remove_character(character: DialogicCharacter) -> void: - var character_node := get_character_portrait_node(character) + var character_node := get_character_node(character) if is_instance_valid(character_node) and character_node is Node: character_node.queue_free() From 5573d12a6f1a90bc2702796e6cfafafc712f1f1d Mon Sep 17 00:00:00 2001 From: Cake Date: Wed, 13 Mar 2024 15:46:04 +0100 Subject: [PATCH 39/55] Add support for differently sized layers. --- .../LayeredPortrait/layered_portrait.gd | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd b/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd index a6ff1aa3a..1fcd882aa 100644 --- a/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd +++ b/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd @@ -39,10 +39,11 @@ func _update_portrait(passed_character: DialogicCharacter, passed_portrait: Stri ## This method is not changing the scene itself and is intended for the ## Dialogic editor preview and in-game rendering only. func _apply_layer_adjustments() -> void: + var coverage := _find_largest_coverage_rect() + for sprite: Sprite2D in _find_sprites_recursively(self): - sprite.scale = Vector2.ONE sprite.centered = false - sprite.position = (sprite.get_rect().size * Vector2(-0.5, -1)) + sprite.position + sprite.position = (coverage.size * Vector2(-0.5, -1)) + sprite.position ## Iterates over all children in [param start_node] and its children, looking @@ -192,12 +193,15 @@ func _find_largest_coverage_rect() -> Rect2: var coverage_rect := Rect2(0, 0, 0, 0) for sprite: Sprite2D in _find_sprites_recursively(self): - var sprite_width := sprite.texture.get_width() - var sprite_height := sprite.texture.get_height() + var sprite_size := sprite.get_rect().size + var sprite_position := sprite.position + + var sprite_width := sprite_size.x * sprite.scale.x + var sprite_height := sprite_size.y * sprite.scale.y var texture_rect := Rect2( - sprite.position.x, - sprite.position.y, + sprite_position.x, + sprite_position.y, sprite_width, sprite_height ) From 0f19f82f5612b45568c09f7659ee69eb18bc9d6a Mon Sep 17 00:00:00 2001 From: Cake Date: Wed, 13 Mar 2024 23:19:26 +0100 Subject: [PATCH 40/55] Add support for more types in layered portraits. --- .../Character/LayeredPortrait/layered_portrait.gd | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd b/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd index 1fcd882aa..530a3c132 100644 --- a/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd +++ b/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd @@ -41,9 +41,13 @@ func _update_portrait(passed_character: DialogicCharacter, passed_portrait: Stri func _apply_layer_adjustments() -> void: var coverage := _find_largest_coverage_rect() - for sprite: Sprite2D in _find_sprites_recursively(self): - sprite.centered = false - sprite.position = (coverage.size * Vector2(-0.5, -1)) + sprite.position + for node: Node in get_children(): + + if node is Sprite2D: + var sprite := node as Sprite2D + sprite.centered = false + + node.position = (coverage.size * Vector2(-0.5, -1)) + node.position ## Iterates over all children in [param start_node] and its children, looking @@ -62,8 +66,7 @@ func _find_sprites_recursively(start_node: Node) -> Array[Sprite2D]: if sprite.texture: sprites.append(sprite) - var child_sprites := _find_sprites_recursively(child) - sprites.append_array(child_sprites) + sprites.append_array(sprites) return sprites From fdad0ac17d3e74158f939aae9ba3aaf04296720d Mon Sep 17 00:00:00 2001 From: Cake Date: Thu, 14 Mar 2024 08:35:48 +0100 Subject: [PATCH 41/55] Add support for special resources ending on `in` or `out`. --- addons/dialogic/Core/DialogicResourceUtil.gd | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/addons/dialogic/Core/DialogicResourceUtil.gd b/addons/dialogic/Core/DialogicResourceUtil.gd index 95116f996..88b3d89dc 100644 --- a/addons/dialogic/Core/DialogicResourceUtil.gd +++ b/addons/dialogic/Core/DialogicResourceUtil.gd @@ -183,10 +183,24 @@ static func guess_special_resource(type: String, name: String, default := "") -> return name for path: String in list_special_resources_of_type(type): + var pretty_path := DialogicUtil.pretty_name(path).to_lower() + var pretty_name := name.to_lower() - if DialogicUtil.pretty_name(path).to_lower() == name.to_lower(): + if pretty_path == pretty_name: return path + elif pretty_name.ends_with(" in"): + pretty_name = pretty_name + " out" + + if pretty_path == pretty_name: + return path + + elif pretty_name.ends_with(" out"): + pretty_name = pretty_name.replace("out", "in out") + + if pretty_path == pretty_name: + return path + return default From 3d154e36172f307587ab3b82a1ca16aa2c4e5ca2 Mon Sep 17 00:00:00 2001 From: Cake Date: Thu, 14 Mar 2024 12:20:59 +0100 Subject: [PATCH 42/55] Add fixes to the positioning and coverage rect. --- .../LayeredPortrait/layered_portrait.gd | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd b/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd index 530a3c132..4086caa07 100644 --- a/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd +++ b/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd @@ -20,6 +20,9 @@ static var _REGEX := RegEx.create_from_string(_REGEX_STRING) var _initialized := false +var _is_coverage_rect_cached := false +var _cached_coverage_rect := Rect2(0, 0, 0, 0) + ## Overriding [class DialogicPortrait]'s method. ## @@ -47,7 +50,15 @@ func _apply_layer_adjustments() -> void: var sprite := node as Sprite2D sprite.centered = false - node.position = (coverage.size * Vector2(-0.5, -1)) + node.position + var node_position: Vector2 = node.position + node.position = _reposition_with_rect(coverage, node_position) + + +## Returns a position based on [param rect]'s size where Dialogic expects the +## scene part to be positioned at. [br] +## If the node has an offset or extra position, pass it as [param node_offset]. +func _reposition_with_rect(rect: Rect2, node_offset := Vector2(0.0, 0.0)) -> Vector2: + return rect.size * Vector2(-0.5, -1.0) + node_offset ## Iterates over all children in [param start_node] and its children, looking @@ -66,7 +77,9 @@ func _find_sprites_recursively(start_node: Node) -> Array[Sprite2D]: if sprite.texture: sprites.append(sprite) - sprites.append_array(sprites) + + var sub := _find_sprites_recursively(child) + sprites.append_array(sub) return sprites @@ -193,6 +206,9 @@ func _set_mirror(is_mirrored: bool) -> void: ## Scans all nodes in this scene and finds the largest rectangle that ## covers encloses every sprite. func _find_largest_coverage_rect() -> Rect2: + if _is_coverage_rect_cached: + return _cached_coverage_rect + var coverage_rect := Rect2(0, 0, 0, 0) for sprite: Sprite2D in _find_sprites_recursively(self): @@ -208,9 +224,14 @@ func _find_largest_coverage_rect() -> Rect2: sprite_width, sprite_height ) - coverage_rect = coverage_rect.merge(texture_rect) + + coverage_rect.position = _reposition_with_rect(coverage_rect) + + _is_coverage_rect_cached = true + _cached_coverage_rect = coverage_rect + return coverage_rect From cced075c62b4ccbb7439ef2036544413d6a37b5c Mon Sep 17 00:00:00 2001 From: Cake Date: Sat, 16 Mar 2024 08:03:33 +0100 Subject: [PATCH 43/55] Fix mirror logic. --- .../Character/LayeredPortrait/layered_portrait.gd | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd b/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd index 4086caa07..bb325443d 100644 --- a/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd +++ b/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd @@ -45,11 +45,6 @@ func _apply_layer_adjustments() -> void: var coverage := _find_largest_coverage_rect() for node: Node in get_children(): - - if node is Sprite2D: - var sprite := node as Sprite2D - sprite.centered = false - var node_position: Vector2 = node.position node.position = _reposition_with_rect(coverage, node_position) @@ -199,8 +194,11 @@ func _set_extra_data(data: String) -> void: ## ## Handling all layers horizontal flip state. func _set_mirror(is_mirrored: bool) -> void: - for sprite: Sprite2D in _find_sprites_recursively(self): - sprite.flip_h = is_mirrored + for child: Node in get_children(): + + if is_mirrored: + child.position.x = child.position.x * -1 + child.scale.x = -child.scale.x ## Scans all nodes in this scene and finds the largest rectangle that @@ -229,6 +227,9 @@ func _find_largest_coverage_rect() -> Rect2: coverage_rect.position = _reposition_with_rect(coverage_rect) + #var origin := get("position") + #coverage_rect.expand(origin) + _is_coverage_rect_cached = true _cached_coverage_rect = coverage_rect From 70d2717722a1f17dceaef8f89e90543dab9483bb Mon Sep 17 00:00:00 2001 From: Cake Date: Tue, 19 Mar 2024 10:35:43 +0100 Subject: [PATCH 44/55] Use latest portrait node for all operations. --- .../Modules/Character/subsystem_portraits.gd | 49 +++++++++++-------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/addons/dialogic/Modules/Character/subsystem_portraits.gd b/addons/dialogic/Modules/Character/subsystem_portraits.gd index 578500a1b..9740ca533 100644 --- a/addons/dialogic/Modules/Character/subsystem_portraits.gd +++ b/addons/dialogic/Modules/Character/subsystem_portraits.gd @@ -126,17 +126,22 @@ func _change_portrait(character_node: Node2D, portrait: String, update_transform print_debug('[Dialogic] Change to not-existing portrait will be ignored!') return info - # path to the scene to use + # Path to the scene to use. var scene_path: String = character.portraits[portrait].get('scene', '') var portrait_node: Node = null - - # check if the scene is the same as the currently loaded scene - if (character_node.get_child_count() and - character_node.get_child(0).get_meta('scene', '') == scene_path and - # also check if the scene supports changing to the given portrait - (!character_node.get_child(0).has_method('_should_do_portrait_update') or character_node.get_child(0)._should_do_portrait_update(character, portrait))): - portrait_node = character_node.get_child(0) + var latest_portrait: Node = null + + if character_node.get_child_count() > 0: + latest_portrait = character_node.get_child(-1) + + # Check if the scene is the same as the currently loaded scene. + if (not latest_portrait == null and + latest_portrait.get_meta('scene', '') == scene_path and + # Also check if the scene supports changing to the given portrait. + (not latest_portrait.has_method('_should_do_portrait_update') + or latest_portrait._should_do_portrait_update(character, portrait))): + portrait_node = latest_portrait info['same_scene'] = true else: @@ -172,16 +177,20 @@ func _change_portrait(character_node: Node2D, portrait: String, update_transform ## Changes the mirroring of the given portrait. ## Unless @force is false, this will take into consideration the character mirror, ## portrait mirror and portrait position mirror settings. -func _change_portrait_mirror(character_node:Node2D, mirrored:=false, force:=false) -> void: - if character_node.get_child(0).has_method('_set_mirror'): +func _change_portrait_mirror(character_node: Node2D, mirrored := false, force := false) -> void: + var latest_portrait := character_node.get_child(-1) + + if latest_portrait.has_method('_set_mirror'): var character: DialogicCharacter= character_node.get_meta('character') var current_portrait_info := character.get_portrait_info(character_node.get_meta('portrait')) - character_node.get_child(0)._set_mirror(force or (mirrored != character.mirror != character_node.get_parent().mirrored != current_portrait_info.get('mirror', false))) + latest_portrait._set_mirror(force or (mirrored != character.mirror != character_node.get_parent().mirrored != current_portrait_info.get('mirror', false))) func _change_portrait_extradata(character_node:Node2D, extra_data:="") -> void: - if character_node.get_child(0).has_method('_set_extra_data'): - character_node.get_child(0)._set_extra_data(extra_data) + var latest_portrait := character_node.get_child(-1) + + if latest_portrait.has_method('_set_extra_data'): + latest_portrait._set_extra_data(extra_data) func _update_portrait_transform(portrait_node: Node, time:float = 0.0) -> void: @@ -665,28 +674,28 @@ func change_speaker(speaker: DialogicCharacter = null, portrait := "") -> void: if portrait.is_empty(): portrait = speaker.default_portrait if container.portrait_prefix+portrait in speaker.portraits: - _change_portrait(container.get_child(0), container.portrait_prefix+portrait) + _change_portrait(container.get_child(-1), container.portrait_prefix+portrait) else: - _change_portrait(container.get_child(0), portrait) + _change_portrait(container.get_child(-1), portrait) # if the character has no portraits _change_portrait won't actually add a child node - if container.get_child(0).get_child_count() == 0: + if container.get_child(-1).get_child_count() == 0: continue - _change_portrait_mirror(container.get_child(0)) + _change_portrait_mirror(container.get_child(-1)) if speaker: if speaker.resource_path != dialogic.current_state_info['speaker']: if dialogic.current_state_info['speaker'] and is_character_joined(load(dialogic.current_state_info['speaker'])): - dialogic.current_state_info['portraits'][dialogic.current_state_info['speaker']].node.get_child(0)._unhighlight() + dialogic.current_state_info['portraits'][dialogic.current_state_info['speaker']].node.get_child(-1)._unhighlight() if speaker and is_character_joined(speaker): - dialogic.current_state_info['portraits'][speaker.resource_path].node.get_child(0)._highlight() + dialogic.current_state_info['portraits'][speaker.resource_path].node.get_child(-1)._highlight() elif dialogic.current_state_info['speaker'] and is_character_joined(load(dialogic.current_state_info['speaker'])): - dialogic.current_state_info['portraits'][dialogic.current_state_info['speaker']].node.get_child(0)._unhighlight() + dialogic.current_state_info['portraits'][dialogic.current_state_info['speaker']].node.get_child(-1)._unhighlight() #endregion From 6e78c47e9ca5299c88558df6a367ccf8cbf71bba Mon Sep 17 00:00:00 2001 From: Cake Date: Thu, 21 Mar 2024 15:12:39 +0100 Subject: [PATCH 45/55] Make all transition animations reversible. --- .../Character/DefaultAnimations/bounce_in.gd | 13 ------- .../DefaultAnimations/bounce_in_out.gd | 32 ++++++++++++++++ .../Character/DefaultAnimations/bounce_out.gd | 15 -------- .../DefaultAnimations/fade_down_in_out.gd | 38 +++++++++++++++++++ .../DefaultAnimations/fade_in_out.gd | 21 +++++----- .../DefaultAnimations/fade_out_down.gd | 12 ------ .../{fade_in_up.gd => fade_up_in_out.gd} | 3 +- ...instant_in_or_out.gd => instant_in_out.gd} | 4 +- .../DefaultAnimations/slide_down_in_out.gd | 19 ++++++++++ .../DefaultAnimations/slide_in_down.gd | 10 ----- .../DefaultAnimations/slide_in_left.gd | 10 ----- .../DefaultAnimations/slide_in_right.gd | 10 ----- .../DefaultAnimations/slide_in_up.gd | 10 ----- .../DefaultAnimations/slide_left_in_out.gd | 19 ++++++++++ .../DefaultAnimations/slide_out_down.gd | 9 ----- .../DefaultAnimations/slide_out_left.gd | 9 ----- .../DefaultAnimations/slide_out_right.gd | 9 ----- .../DefaultAnimations/slide_out_up.gd | 9 ----- .../DefaultAnimations/slide_right_in_out.gd | 20 ++++++++++ .../DefaultAnimations/slide_up_in.gd | 18 +++++++++ .../DefaultAnimations/zoom_center_in_out.gd | 29 ++++++++++++++ .../Character/DefaultAnimations/zoom_in.gd | 15 -------- .../DefaultAnimations/zoom_in_center.gd | 15 -------- .../DefaultAnimations/zoom_in_out.gd | 28 ++++++++++++++ .../Character/DefaultAnimations/zoom_out.gd | 13 ------- .../DefaultAnimations/zoom_out_center.gd | 12 ------ 26 files changed, 216 insertions(+), 186 deletions(-) delete mode 100644 addons/dialogic/Modules/Character/DefaultAnimations/bounce_in.gd create mode 100644 addons/dialogic/Modules/Character/DefaultAnimations/bounce_in_out.gd delete mode 100644 addons/dialogic/Modules/Character/DefaultAnimations/bounce_out.gd create mode 100644 addons/dialogic/Modules/Character/DefaultAnimations/fade_down_in_out.gd delete mode 100644 addons/dialogic/Modules/Character/DefaultAnimations/fade_out_down.gd rename addons/dialogic/Modules/Character/DefaultAnimations/{fade_in_up.gd => fade_up_in_out.gd} (97%) rename addons/dialogic/Modules/Character/DefaultAnimations/{instant_in_or_out.gd => instant_in_out.gd} (60%) create mode 100644 addons/dialogic/Modules/Character/DefaultAnimations/slide_down_in_out.gd delete mode 100644 addons/dialogic/Modules/Character/DefaultAnimations/slide_in_down.gd delete mode 100644 addons/dialogic/Modules/Character/DefaultAnimations/slide_in_left.gd delete mode 100644 addons/dialogic/Modules/Character/DefaultAnimations/slide_in_right.gd delete mode 100644 addons/dialogic/Modules/Character/DefaultAnimations/slide_in_up.gd create mode 100644 addons/dialogic/Modules/Character/DefaultAnimations/slide_left_in_out.gd delete mode 100644 addons/dialogic/Modules/Character/DefaultAnimations/slide_out_down.gd delete mode 100644 addons/dialogic/Modules/Character/DefaultAnimations/slide_out_left.gd delete mode 100644 addons/dialogic/Modules/Character/DefaultAnimations/slide_out_right.gd delete mode 100644 addons/dialogic/Modules/Character/DefaultAnimations/slide_out_up.gd create mode 100644 addons/dialogic/Modules/Character/DefaultAnimations/slide_right_in_out.gd create mode 100644 addons/dialogic/Modules/Character/DefaultAnimations/slide_up_in.gd create mode 100644 addons/dialogic/Modules/Character/DefaultAnimations/zoom_center_in_out.gd delete mode 100644 addons/dialogic/Modules/Character/DefaultAnimations/zoom_in.gd delete mode 100644 addons/dialogic/Modules/Character/DefaultAnimations/zoom_in_center.gd create mode 100644 addons/dialogic/Modules/Character/DefaultAnimations/zoom_in_out.gd delete mode 100644 addons/dialogic/Modules/Character/DefaultAnimations/zoom_out.gd delete mode 100644 addons/dialogic/Modules/Character/DefaultAnimations/zoom_out_center.gd diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/bounce_in.gd b/addons/dialogic/Modules/Character/DefaultAnimations/bounce_in.gd deleted file mode 100644 index 941d1e4e9..000000000 --- a/addons/dialogic/Modules/Character/DefaultAnimations/bounce_in.gd +++ /dev/null @@ -1,13 +0,0 @@ -extends DialogicAnimation - -func animate(): - var tween := (node.create_tween() as Tween) - node.scale = Vector2() - node.modulate.a = 0 - - tween.set_ease(Tween.EASE_IN_OUT) - tween.set_trans(Tween.TRANS_SINE) - tween.set_parallel() - tween.tween_property(node, 'scale', Vector2(1,1), time).set_trans(Tween.TRANS_SPRING).set_ease(Tween.EASE_OUT) - tween.tween_property(node, 'modulate:a', 1.0, time) - tween.finished.connect(emit_signal.bind('finished_once')) diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/bounce_in_out.gd b/addons/dialogic/Modules/Character/DefaultAnimations/bounce_in_out.gd new file mode 100644 index 000000000..0f487bf33 --- /dev/null +++ b/addons/dialogic/Modules/Character/DefaultAnimations/bounce_in_out.gd @@ -0,0 +1,32 @@ +extends DialogicAnimation + + +func animate() -> void: + var tween := (node.create_tween() as Tween) + + var end_scale: Vector2 = node.scale + var end_modulate_alpha := 1.0 + var modulation_property := get_modulation_property() + + if is_reversed: + end_scale = Vector2(0, 0) + end_modulate_alpha = 0.0 + + else: + node.scale = Vector2(0, 0) + var original_modulation: Color = node.get(modulation_property) + original_modulation.a = 0.0 + node.set(modulation_property, original_modulation) + + + tween.set_ease(Tween.EASE_IN_OUT) + tween.set_trans(Tween.TRANS_SINE) + tween.set_parallel() + + (tween.tween_property(node, "scale", end_scale, time) + .set_trans(Tween.TRANS_SPRING) + .set_ease(Tween.EASE_OUT)) + tween.tween_property(node, modulation_property + ":a", end_modulate_alpha, time) + + await tween.finished + finished_once.emit() diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/bounce_out.gd b/addons/dialogic/Modules/Character/DefaultAnimations/bounce_out.gd deleted file mode 100644 index bc9764282..000000000 --- a/addons/dialogic/Modules/Character/DefaultAnimations/bounce_out.gd +++ /dev/null @@ -1,15 +0,0 @@ -extends DialogicAnimation - -func animate(): - var tween := (node.create_tween() as Tween) - node.scale = Vector2(1,1) - node.modulate.a = 1 - - tween.set_ease(Tween.EASE_IN_OUT) - tween.set_trans(Tween.TRANS_LINEAR) - tween.set_parallel() - - tween.tween_property(node, 'scale', Vector2(), time).set_trans(Tween.TRANS_ELASTIC).set_ease(Tween.EASE_IN) - tween.tween_property(node, 'modulate:a', 0.0, time) - - tween.finished.connect(emit_signal.bind('finished_once')) diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/fade_down_in_out.gd b/addons/dialogic/Modules/Character/DefaultAnimations/fade_down_in_out.gd new file mode 100644 index 000000000..92c80137f --- /dev/null +++ b/addons/dialogic/Modules/Character/DefaultAnimations/fade_down_in_out.gd @@ -0,0 +1,38 @@ +extends DialogicAnimation + +func animate() -> void: + var tween := (node.create_tween() as Tween) + + var start_height: float = orig_pos.y - node.get_viewport().size.y / 5 + var end_height := orig_pos.y + + var start_modulation := 0.0 + var end_modulation := 1.0 + + if is_reversed: + end_height = start_height + start_height = orig_pos.y + end_modulation = 0.0 + start_modulation = 1.0 + + node.position.y = start_height + + tween.set_ease(Tween.EASE_OUT) + tween.set_trans(Tween.TRANS_SINE) + tween.set_parallel() + + var end_postion := Vector2(orig_pos.x, end_height) + tween.tween_property(node, "position", end_postion, time) + + var property := get_modulation_property() + + var original_modulation: Color = node.get(property) + original_modulation.a = start_modulation + node.set(property, original_modulation) + var modulation_alpha := property + ":a" + + tween.tween_property(node, modulation_alpha, end_modulation, time) + + await tween.finished + finished_once.emit() + diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/fade_in_out.gd b/addons/dialogic/Modules/Character/DefaultAnimations/fade_in_out.gd index 5a8bb0d4d..1a2723b9d 100644 --- a/addons/dialogic/Modules/Character/DefaultAnimations/fade_in_out.gd +++ b/addons/dialogic/Modules/Character/DefaultAnimations/fade_in_out.gd @@ -1,23 +1,22 @@ extends DialogicAnimation func animate() -> void: - var tween := (node.create_tween() as Tween) - var start := 0.0 - var end := 1.0 + var modulation_property := get_modulation_property() + var end_modulation_alpha := 1.0 if is_reversed: - start = 1.0 - end = 0.0 + end_modulation_alpha = 0.0 - var property := get_modulation_property() - var original_color: Color = node.get(property) - original_color.a = start - node.set(property, original_color) + else: + var original_modulation: Color = node.get(modulation_property) + original_modulation.a = 0.0 + node.set(modulation_property, original_modulation) - tween.set_ease(Tween.EASE_OUT) + var tween := (node.create_tween() as Tween) + tween.set_ease(Tween.EASE_IN) tween.set_trans(Tween.TRANS_SINE) - tween.tween_property(node, property + ":a", end, time) + tween.tween_property(node, modulation_property + ":a", end_modulation_alpha, time) await tween.finished finished_once.emit() diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/fade_out_down.gd b/addons/dialogic/Modules/Character/DefaultAnimations/fade_out_down.gd deleted file mode 100644 index e0d8c4fa2..000000000 --- a/addons/dialogic/Modules/Character/DefaultAnimations/fade_out_down.gd +++ /dev/null @@ -1,12 +0,0 @@ -extends DialogicAnimation - -func animate() -> void: - var tween := (node.create_tween() as Tween) - tween.set_ease(Tween.EASE_IN_OUT) - tween.set_trans(Tween.TRANS_SINE) - tween.set_parallel() - - tween.tween_property(node, 'position:y', orig_pos.y + node.get_viewport().size.y/5, time) - tween.tween_property(node, 'modulate:a', 0.0, time) - - tween.finished.connect(emit_signal.bind(finished_once)) diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/fade_in_up.gd b/addons/dialogic/Modules/Character/DefaultAnimations/fade_up_in_out.gd similarity index 97% rename from addons/dialogic/Modules/Character/DefaultAnimations/fade_in_up.gd rename to addons/dialogic/Modules/Character/DefaultAnimations/fade_up_in_out.gd index 2ed6c6444..6f23292a3 100644 --- a/addons/dialogic/Modules/Character/DefaultAnimations/fade_in_up.gd +++ b/addons/dialogic/Modules/Character/DefaultAnimations/fade_up_in_out.gd @@ -34,5 +34,4 @@ func animate() -> void: tween.tween_property(node, modulation_alpha, end_modulation, time) await tween.finished - finished_once.emit() - + finished_once.emit() \ No newline at end of file diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/instant_in_or_out.gd b/addons/dialogic/Modules/Character/DefaultAnimations/instant_in_out.gd similarity index 60% rename from addons/dialogic/Modules/Character/DefaultAnimations/instant_in_or_out.gd rename to addons/dialogic/Modules/Character/DefaultAnimations/instant_in_out.gd index 873225bb5..b6ec6e482 100644 --- a/addons/dialogic/Modules/Character/DefaultAnimations/instant_in_or_out.gd +++ b/addons/dialogic/Modules/Character/DefaultAnimations/instant_in_out.gd @@ -1,5 +1,5 @@ extends DialogicAnimation -func animate(): +func animate() -> void: await node.get_tree().process_frame - emit_signal('finished') + finished.emit() diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/slide_down_in_out.gd b/addons/dialogic/Modules/Character/DefaultAnimations/slide_down_in_out.gd new file mode 100644 index 000000000..d76746f83 --- /dev/null +++ b/addons/dialogic/Modules/Character/DefaultAnimations/slide_down_in_out.gd @@ -0,0 +1,19 @@ +extends DialogicAnimation + +func animate() -> void: + var tween := (node.create_tween() as Tween) + tween.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_BACK) + + var target_position := end_position.y + var start_position: float = -node.get_viewport().size.y + + if is_reversed: + target_position = -node.get_viewport().size.y + start_position = end_position.y + + node.position.y = start_position + + tween.tween_property(node, 'position:y', target_position, time) + + await tween.finished + finished_once.emit() diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/slide_in_down.gd b/addons/dialogic/Modules/Character/DefaultAnimations/slide_in_down.gd deleted file mode 100644 index d448e1284..000000000 --- a/addons/dialogic/Modules/Character/DefaultAnimations/slide_in_down.gd +++ /dev/null @@ -1,10 +0,0 @@ -extends DialogicAnimation - -func animate(): - var tween := (node.create_tween() as Tween) - tween.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_BACK) - - node.position.y = -node.get_viewport().size.y - tween.tween_property(node, 'position:y', end_position.y, time) - - tween.finished.connect(emit_signal.bind('finished_once')) diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/slide_in_left.gd b/addons/dialogic/Modules/Character/DefaultAnimations/slide_in_left.gd deleted file mode 100644 index 77bf984f3..000000000 --- a/addons/dialogic/Modules/Character/DefaultAnimations/slide_in_left.gd +++ /dev/null @@ -1,10 +0,0 @@ -extends DialogicAnimation - -func animate(): - var tween := (node.create_tween() as Tween) - tween.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_BACK) - - node.position.x = -node.get_viewport().size.x/5 - tween.tween_property(node, 'position:x', end_position.x, time) - - tween.finished.connect(emit_signal.bind('finished_once')) diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/slide_in_right.gd b/addons/dialogic/Modules/Character/DefaultAnimations/slide_in_right.gd deleted file mode 100644 index 7df50cdc2..000000000 --- a/addons/dialogic/Modules/Character/DefaultAnimations/slide_in_right.gd +++ /dev/null @@ -1,10 +0,0 @@ -extends DialogicAnimation - -func animate(): - var tween := (node.create_tween() as Tween) - tween.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_BACK) - - node.position.x = node.get_viewport().size.x+node.get_viewport().size.x/5 - tween.tween_property(node, 'position:x', end_position.x, time) - - tween.finished.connect(emit_signal.bind('finished_once')) diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/slide_in_up.gd b/addons/dialogic/Modules/Character/DefaultAnimations/slide_in_up.gd deleted file mode 100644 index 9c48695a4..000000000 --- a/addons/dialogic/Modules/Character/DefaultAnimations/slide_in_up.gd +++ /dev/null @@ -1,10 +0,0 @@ -extends DialogicAnimation - -func animate(): - var tween := (node.create_tween() as Tween) - tween.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_BACK) - - node.position.y = node.get_viewport().size.y*2 - tween.tween_property(node, 'position:y', end_position.y, time) - - tween.finished.connect(emit_signal.bind('finished_once')) diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/slide_left_in_out.gd b/addons/dialogic/Modules/Character/DefaultAnimations/slide_left_in_out.gd new file mode 100644 index 000000000..766707209 --- /dev/null +++ b/addons/dialogic/Modules/Character/DefaultAnimations/slide_left_in_out.gd @@ -0,0 +1,19 @@ +extends DialogicAnimation + + +func animate() -> void: + var tween := (node.create_tween() as Tween) + tween.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_BACK) + + var end_position_x: float = node.position.x + + if is_reversed: + end_position_x = -node.get_viewport().size.x / 2 + + else: + node.position.x = -node.get_viewport().size.x / 5 + + tween.tween_property(node, 'position:x', end_position_x, time) + + await tween.finished + finished_once.emit() diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/slide_out_down.gd b/addons/dialogic/Modules/Character/DefaultAnimations/slide_out_down.gd deleted file mode 100644 index 66de11015..000000000 --- a/addons/dialogic/Modules/Character/DefaultAnimations/slide_out_down.gd +++ /dev/null @@ -1,9 +0,0 @@ -extends DialogicAnimation - -func animate(): - var tween := (node.create_tween() as Tween) - tween.set_ease(Tween.EASE_IN).set_trans(Tween.TRANS_EXPO) - - tween.tween_property(node, 'position:y', node.get_viewport().size.y*2, time) - - tween.finished.connect(emit_signal.bind('finished_once')) diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/slide_out_left.gd b/addons/dialogic/Modules/Character/DefaultAnimations/slide_out_left.gd deleted file mode 100644 index 123b84412..000000000 --- a/addons/dialogic/Modules/Character/DefaultAnimations/slide_out_left.gd +++ /dev/null @@ -1,9 +0,0 @@ -extends DialogicAnimation - -func animate(): - var tween := (node.create_tween() as Tween) - tween.set_ease(Tween.EASE_IN).set_trans(Tween.TRANS_EXPO) - - tween.tween_property(node, 'position:x', -node.get_viewport().size.x/5, time) - - tween.finished.connect(emit_signal.bind('finished_once')) diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/slide_out_right.gd b/addons/dialogic/Modules/Character/DefaultAnimations/slide_out_right.gd deleted file mode 100644 index 102412470..000000000 --- a/addons/dialogic/Modules/Character/DefaultAnimations/slide_out_right.gd +++ /dev/null @@ -1,9 +0,0 @@ -extends DialogicAnimation - -func animate(): - var tween := (node.create_tween() as Tween) - tween.set_ease(Tween.EASE_IN).set_trans(Tween.TRANS_EXPO) - - tween.tween_property(node, 'position:x', node.get_viewport().size.x+node.get_viewport().size.x/5, time) - - tween.finished.connect(emit_signal.bind('finished_once')) diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/slide_out_up.gd b/addons/dialogic/Modules/Character/DefaultAnimations/slide_out_up.gd deleted file mode 100644 index 1eeeeeffd..000000000 --- a/addons/dialogic/Modules/Character/DefaultAnimations/slide_out_up.gd +++ /dev/null @@ -1,9 +0,0 @@ -extends DialogicAnimation - -func animate(): - var tween := (node.create_tween() as Tween) - tween.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_EXPO) - - tween.tween_property(node, 'position:y', -node.get_viewport().size.y, time) - - tween.finished.connect(emit_signal.bind('finished_once')) diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/slide_right_in_out.gd b/addons/dialogic/Modules/Character/DefaultAnimations/slide_right_in_out.gd new file mode 100644 index 000000000..3568a2069 --- /dev/null +++ b/addons/dialogic/Modules/Character/DefaultAnimations/slide_right_in_out.gd @@ -0,0 +1,20 @@ +extends DialogicAnimation + +func animate() -> void: + var tween := (node.create_tween() as Tween) + tween.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_BACK) + + var viewport_x: float = node.get_viewport().size.x + + var start_position_x: float = viewport_x + viewport_x / 5 + var end_position_x := end_position.x + + if is_reversed: + start_position_x = end_position.x + end_position_x = viewport_x + node.get_viewport().size.x / 5 + + + node.position.x = start_position_x + tween.tween_property(node, 'position:x', end_position_x, time) + + tween.finished.connect(emit_signal.bind('finished_once')) diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/slide_up_in.gd b/addons/dialogic/Modules/Character/DefaultAnimations/slide_up_in.gd new file mode 100644 index 000000000..d0928b01e --- /dev/null +++ b/addons/dialogic/Modules/Character/DefaultAnimations/slide_up_in.gd @@ -0,0 +1,18 @@ +extends DialogicAnimation + +func animate() -> void: + var tween := (node.create_tween() as Tween) + tween.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_BACK) + + var start_position_y: float = node.get_viewport().size.y * 2 + var end_position_y := end_position.y + + if is_reversed: + start_position_y = end_position.y + end_position_y = node.get_viewport().size.y * 2 + + node.position.y = start_position_y + tween.tween_property(node, 'position:y', end_position_y, time) + + await tween.finished + finished_once.emit() diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/zoom_center_in_out.gd b/addons/dialogic/Modules/Character/DefaultAnimations/zoom_center_in_out.gd new file mode 100644 index 000000000..95dd52f2e --- /dev/null +++ b/addons/dialogic/Modules/Character/DefaultAnimations/zoom_center_in_out.gd @@ -0,0 +1,29 @@ +extends DialogicAnimation + +func animate() -> void: + var modulate_property := get_modulation_property() + var modulate_alpha_property := modulate_property + ":a" + + var end_scale: Vector2 = node.scale + var end_modulation_alpha := 1.0 + + if is_reversed: + end_modulation_alpha = 0.0 + + else: + node.scale = Vector2(0, 0) + node.position.y = end_position.y - node.get_viewport().size.y * 0.5 + + var original_modulation: Color = node.get(modulate_property) + original_modulation.a = 0.0 + node.set(modulate_property, original_modulation) + + var tween := (node.create_tween() as Tween) + tween.set_ease(Tween.EASE_IN_OUT).set_trans(Tween.TRANS_EXPO) + tween.set_parallel(true) + tween.tween_property(node, "scale", end_scale, time) + tween.tween_property(node, "position", end_position, time) + tween.tween_property(node, modulate_alpha_property, end_modulation_alpha, time) + + await tween.finished + finished_once.emit() diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/zoom_in.gd b/addons/dialogic/Modules/Character/DefaultAnimations/zoom_in.gd deleted file mode 100644 index 749c2db7a..000000000 --- a/addons/dialogic/Modules/Character/DefaultAnimations/zoom_in.gd +++ /dev/null @@ -1,15 +0,0 @@ -extends DialogicAnimation - -func animate(): - var tween := (node.create_tween() as Tween) - node.scale = Vector2(0,0) - node.modulate.a = 0 - tween.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_EXPO) - tween.set_parallel(true) - -# node.position.y = node.get_viewport().size.y/2 - tween.tween_property(node, 'scale', Vector2(1,1), time) -# tween.tween_property(node, 'position:y', end_position.y, time) - tween.tween_property(node, 'modulate:a', 1, time) - - tween.finished.connect(emit_signal.bind('finished_once')) diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/zoom_in_center.gd b/addons/dialogic/Modules/Character/DefaultAnimations/zoom_in_center.gd deleted file mode 100644 index 7472a196c..000000000 --- a/addons/dialogic/Modules/Character/DefaultAnimations/zoom_in_center.gd +++ /dev/null @@ -1,15 +0,0 @@ -extends DialogicAnimation - -func animate(): - var tween := (node.create_tween() as Tween) - node.scale = Vector2(0,0) - node.modulate.a = 0 - node.position = node.get_parent().size/2 - tween.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_EXPO) - tween.set_parallel(true) - - tween.tween_property(node, 'scale', Vector2(1,1), time) - tween.tween_property(node, 'position', end_position, time) - tween.tween_property(node, 'modulate:a', 1, time) - - tween.finished.connect(emit_signal.bind('finished_once')) diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/zoom_in_out.gd b/addons/dialogic/Modules/Character/DefaultAnimations/zoom_in_out.gd new file mode 100644 index 000000000..5d7a9507f --- /dev/null +++ b/addons/dialogic/Modules/Character/DefaultAnimations/zoom_in_out.gd @@ -0,0 +1,28 @@ +extends DialogicAnimation + +func animate() -> void: + var modulate_property := get_modulation_property() + var modulate_alpha_property := modulate_property + ":a" + + var end_scale: Vector2 = node.scale + var end_modulation_alpha := 1.0 + + if is_reversed: + end_scale = Vector2(0, 0) + end_modulation_alpha = 0.0 + + else: + node.scale = Vector2(0,0) + + var original_modulation: Color = node.get(modulate_property) + original_modulation.a = 0.0 + node.set(modulate_property, original_modulation) + + var tween := (node.create_tween() as Tween) + tween.set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_EXPO) + tween.set_parallel(true) + tween.tween_property(node, "scale", end_scale, time) + tween.tween_property(node, modulate_alpha_property, end_modulation_alpha, time) + + await tween.finished + finished_once.emit() diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/zoom_out.gd b/addons/dialogic/Modules/Character/DefaultAnimations/zoom_out.gd deleted file mode 100644 index dedf61900..000000000 --- a/addons/dialogic/Modules/Character/DefaultAnimations/zoom_out.gd +++ /dev/null @@ -1,13 +0,0 @@ -extends DialogicAnimation - -func animate(): - var tween := (node.create_tween() as Tween) - tween.set_ease(Tween.EASE_IN).set_trans(Tween.TRANS_EXPO) - tween.set_parallel(true) - -# node.position.y = node.get_viewport().size.y/2 - tween.tween_property(node, 'scale', Vector2(0,0), time) -# tween.tween_property(node, 'position:y', end_position.y, time) - tween.tween_property(node, 'modulate:a', 0, time) - - tween.finished.connect(emit_signal.bind('finished_once')) diff --git a/addons/dialogic/Modules/Character/DefaultAnimations/zoom_out_center.gd b/addons/dialogic/Modules/Character/DefaultAnimations/zoom_out_center.gd deleted file mode 100644 index 1860259e3..000000000 --- a/addons/dialogic/Modules/Character/DefaultAnimations/zoom_out_center.gd +++ /dev/null @@ -1,12 +0,0 @@ -extends DialogicAnimation - -func animate(): - var tween := (node.create_tween() as Tween) - tween.set_ease(Tween.EASE_IN).set_trans(Tween.TRANS_EXPO) - tween.set_parallel(true) - - tween.tween_property(node, 'scale', Vector2(0,0), time) - tween.tween_property(node, 'position', node.get_parent().size/2, time) - tween.tween_property(node, 'modulate:a', 0, time) - - tween.finished.connect(emit_signal.bind('finished_once')) From 2f99c8ca3f9fab21b4966ec0ff2f3d3d9dfa1579 Mon Sep 17 00:00:00 2001 From: Cake Date: Mon, 8 Apr 2024 20:50:17 +0200 Subject: [PATCH 46/55] Fix support of `Node2D`. --- .../Character/LayeredPortrait/layered_portrait.gd | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd b/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd index bb325443d..6ba56af84 100644 --- a/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd +++ b/addons/dialogic/Modules/Character/LayeredPortrait/layered_portrait.gd @@ -103,18 +103,16 @@ class LayerCommand: printerr("Layered Portrait had no node matching the node path: '", _path, "'.") return - if not target_node is Sprite2D: - printerr("Layered Portrait target path '", _path, "', is not a Sprite2D node.") + if not target_node is Node2D and not target_node is Sprite2D: + printerr("Layered Portrait target path '", _path, "', is not a Sprite2D or Node2D type.") return - var sprite := target_node as Sprite2D - match _type: CommandType.SHOW_LAYER: - sprite.show() + target_node.show() CommandType.HIDE_LAYER: - sprite.hide() + target_node.hide() CommandType.SET_LAYER: var target_parent := target_node.get_parent() @@ -125,7 +123,7 @@ class LayerCommand: var sprite_child := child as Sprite2D sprite_child.hide() - sprite.show() + target_node.show() ## Turns the input into a single [class LayerCommand] object. From 83875fc121aef14920e50d0b74cea0ca42ae9b0c Mon Sep 17 00:00:00 2001 From: Cake Date: Wed, 10 Apr 2024 10:46:14 +0200 Subject: [PATCH 47/55] Prevent playing Transition Animations on Portrait Updates. --- .../Modules/Character/event_character.gd | 16 +++++++++++-- .../Modules/Character/subsystem_portraits.gd | 24 +++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/addons/dialogic/Modules/Character/event_character.gd b/addons/dialogic/Modules/Character/event_character.gd index 2b9d24eec..ed2f08235 100644 --- a/addons/dialogic/Modules/Character/event_character.gd +++ b/addons/dialogic/Modules/Character/event_character.gd @@ -141,13 +141,25 @@ func _execute() -> void: dialogic.Portraits.move_character(character, position, final_position_move_time) - if animation_name.is_empty(): + if animation_name.is_empty() and not portrait.is_empty(): animation_name = ProjectSettings.get_setting("dialogic/animations/cross_fade_default", "Fade In Out") animation_length = ProjectSettings.get_setting("dialogic/animations/cross_fade_default_length", 0.5) animation_wait = ProjectSettings.get_setting("dialogic/animations/cross_fade_default_wait", false) - print(animation_name, animation_length, animation_wait) + if animation_name: + + # TODO: Streamline the process to identify whether an animation + # is an action or transition (in/out) animation. + if animation_name.contains(" in") or animation_name.contains(" out"): + var current_portrait: DialogicPortrait = dialogic.Portraits.get_character_portrait(character) + var current_portrait_name := current_portrait.portrait + var is_same_portrait := current_portrait_name == portrait + + if is_same_portrait: + finish() + return + var final_animation_length: float = animation_length var final_animation_repetitions: int = animation_repeats diff --git a/addons/dialogic/Modules/Character/subsystem_portraits.gd b/addons/dialogic/Modules/Character/subsystem_portraits.gd index 9740ca533..46554b636 100644 --- a/addons/dialogic/Modules/Character/subsystem_portraits.gd +++ b/addons/dialogic/Modules/Character/subsystem_portraits.gd @@ -157,6 +157,7 @@ func _change_portrait(character_node: Node2D, portrait: String, update_transform portrait_node.set_meta('scene', scene_path) + if portrait_node: character_node.set_meta('portrait', portrait) @@ -286,6 +287,22 @@ func _change_portrait_z_index(character_node: Node, z_index:int, update_zindex:= idx += 1 +## Checks if [para, character] has joined the scene, if so, returns its +## active [DialogicPortrait] node. +## +## The difference between an active and inactive nodes is whether the node is +## the latest node. [br] +## If a portrait is fading/animating from portrait A and B, both will exist +## in the scene, but only the new portrait is active, even if it is not +## fully visible yet. +func get_character_portrait(character: DialogicCharacter) -> DialogicPortrait: + if is_character_joined(character): + var portrait_node: DialogicPortrait = dialogic.current_state_info['portraits'][character.resource_path].node + return portrait_node + + return null + + func z_sort_portrait_containers(con1: DialogicNode_PortraitContainer, con2: DialogicNode_PortraitContainer) -> bool: if con1.get_meta('z_index', 0) < con2.get_meta('z_index', 0): return true @@ -557,6 +574,13 @@ func remove_character(character: DialogicCharacter) -> void: dialogic.current_state_info['portraits'].erase(character.resource_path) +func get_current_character() -> DialogicCharacter: + if dialogic.current_state_info.get('speaker', null): + return load(dialogic.current_state_info.speaker) + return null + + + ## Returns true if the given character is currently joined. func is_character_joined(character: DialogicCharacter) -> bool: if character == null or not character.resource_path in dialogic.current_state_info['portraits']: From 949a5671f341a9ecf52122356a0f4e747bc8825c Mon Sep 17 00:00:00 2001 From: Cake Date: Wed, 10 Apr 2024 10:49:12 +0200 Subject: [PATCH 48/55] Add Documentation about identifying Transition Animation. --- addons/dialogic/Modules/Character/event_character.gd | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/addons/dialogic/Modules/Character/event_character.gd b/addons/dialogic/Modules/Character/event_character.gd index ed2f08235..576c497af 100644 --- a/addons/dialogic/Modules/Character/event_character.gd +++ b/addons/dialogic/Modules/Character/event_character.gd @@ -151,6 +151,10 @@ func _execute() -> void: # TODO: Streamline the process to identify whether an animation # is an action or transition (in/out) animation. + # + # If this is a transition animation, we want to check if its + # targeting the same portrait scene, then discard the + # animation. if animation_name.contains(" in") or animation_name.contains(" out"): var current_portrait: DialogicPortrait = dialogic.Portraits.get_character_portrait(character) var current_portrait_name := current_portrait.portrait From 21016efc2871d92fcac04eaa27703c8e30ef3645 Mon Sep 17 00:00:00 2001 From: Cake Date: Wed, 10 Apr 2024 10:51:41 +0200 Subject: [PATCH 49/55] Guard against more Transition Name Possibilities. --- addons/dialogic/Modules/Character/event_character.gd | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/dialogic/Modules/Character/event_character.gd b/addons/dialogic/Modules/Character/event_character.gd index 576c497af..83dfa559b 100644 --- a/addons/dialogic/Modules/Character/event_character.gd +++ b/addons/dialogic/Modules/Character/event_character.gd @@ -148,6 +148,7 @@ func _execute() -> void: if animation_name: + var animation_name_lowercase := animation_name.to_lower() # TODO: Streamline the process to identify whether an animation # is an action or transition (in/out) animation. @@ -155,7 +156,7 @@ func _execute() -> void: # If this is a transition animation, we want to check if its # targeting the same portrait scene, then discard the # animation. - if animation_name.contains(" in") or animation_name.contains(" out"): + if animation_name_lowercase.ends_with(" in out"): var current_portrait: DialogicPortrait = dialogic.Portraits.get_character_portrait(character) var current_portrait_name := current_portrait.portrait var is_same_portrait := current_portrait_name == portrait From 2c89fbc085159c4cacc9e83927284e780ab31e91 Mon Sep 17 00:00:00 2001 From: Cake Date: Sun, 14 Apr 2024 14:02:10 +0200 Subject: [PATCH 50/55] Add Unit Tests for Portrait Animations. --- Tests/Unit/guess_special_resource_test.gd | 40 +++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 Tests/Unit/guess_special_resource_test.gd diff --git a/Tests/Unit/guess_special_resource_test.gd b/Tests/Unit/guess_special_resource_test.gd new file mode 100644 index 000000000..14c7574eb --- /dev/null +++ b/Tests/Unit/guess_special_resource_test.gd @@ -0,0 +1,40 @@ +extends GdUnitTestSuite + +## Check if transition animations can be accessed with "in", "out, "in out" +## as space-delimited prefix. +func test_fade_in_animation_paths() -> void: + const TYPE := "PortraitAnimation" + var fade_in_1 := DialogicResourceUtil.guess_special_resource(TYPE, "fade in", "") + var fade_in_2 := DialogicResourceUtil.guess_special_resource(TYPE, "fade in out", "") + var fade_in_3 := DialogicResourceUtil.guess_special_resource(TYPE, "fade out", "") + + var is_any_fade_in_empty := fade_in_1.is_empty() or fade_in_2.is_empty() or fade_in_3.is_empty() + assert(is_any_fade_in_empty == false, "Fade In/Out animations are empty.") + + var are_all_fade_in_equal := fade_in_1 == fade_in_2 and fade_in_2 == fade_in_3 + assert(are_all_fade_in_equal == true, "Fade In/Out animations returned different paths.") + + +## Test if invalid animation paths will return empty strings. +func test_invalid_animation_path() -> void: + const TYPE := "PortraitAnimation" + var invalid_animation_1 := DialogicResourceUtil.guess_special_resource(TYPE, "fade i", "") + assert(invalid_animation_1.is_empty() == true, "Invalid animation 1's path is not empty.") + + + var invalid_animation_2 := DialogicResourceUtil.guess_special_resource(TYPE, "fade", "") + assert(invalid_animation_2.is_empty() == true, "Invalid animation 2's path is not empty.") + + +## Test if invalid types will return empty strings. +func test_invalid_type_path() -> void: + const INVALID_TYPE := "Portait Animation" + var invalid_animation := DialogicResourceUtil.guess_special_resource(INVALID_TYPE, "fade in", "") + assert(invalid_animation.is_empty() == true, "Invalid animation 1's path is not empty.") + + const VALID_TYPE := "PortraitAnimation" + var valid_animation_path := DialogicResourceUtil.guess_special_resource(VALID_TYPE, "fade in", "") + assert(valid_animation_path.is_empty() == false, "Valids animation's path is empty.") + + assert(not invalid_animation == valid_animation_path, "Valid and invalid animation paths are equal.") + From 7db768eebc097e7f404c8ea2d47b4fd0b9730b7a Mon Sep 17 00:00:00 2001 From: Cake Date: Thu, 18 Apr 2024 09:17:30 +0200 Subject: [PATCH 51/55] Fix default animation name order. --- addons/dialogic/Modules/Character/subsystem_portraits.gd | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/dialogic/Modules/Character/subsystem_portraits.gd b/addons/dialogic/Modules/Character/subsystem_portraits.gd index 46554b636..af98264c4 100644 --- a/addons/dialogic/Modules/Character/subsystem_portraits.gd +++ b/addons/dialogic/Modules/Character/subsystem_portraits.gd @@ -349,6 +349,7 @@ func _get_leave_default_length() -> float: ## Adds a character at a position and sets it's portrait. ## If the character is already joined it will only update, portrait, position, etc. func join_character(character:DialogicCharacter, portrait:String, position_idx:int, mirrored:= false, z_index:= 0, extra_data:= "", animation_name:= "", animation_length:= 0.0, animation_wait := false) -> Node: + if is_character_joined(character): change_character_portrait(character, portrait) @@ -381,7 +382,7 @@ func join_character(character:DialogicCharacter, portrait:String, position_idx: character_joined.emit(info) if animation_name.is_empty(): - animation_name = ProjectSettings.get_setting('dialogic/animations/join_default', "Fade In Up") + animation_name = ProjectSettings.get_setting('dialogic/animations/join_default', "Fade Up In") animation_length = _get_join_default_length() animation_wait = ProjectSettings.get_setting('dialogic/animations/join_default_wait', true) From 9c89427d5e6daa5de0a94c8b6626d0f6eeea5e66 Mon Sep 17 00:00:00 2001 From: Cake Date: Thu, 18 Apr 2024 09:21:02 +0200 Subject: [PATCH 52/55] Fix animation name fallback on Character Leave. --- addons/dialogic/Modules/Character/subsystem_portraits.gd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/dialogic/Modules/Character/subsystem_portraits.gd b/addons/dialogic/Modules/Character/subsystem_portraits.gd index af98264c4..bf419389a 100644 --- a/addons/dialogic/Modules/Character/subsystem_portraits.gd +++ b/addons/dialogic/Modules/Character/subsystem_portraits.gd @@ -523,7 +523,7 @@ func leave_character(character: DialogicCharacter, animation_name:= "", animatio if animation_name.is_empty(): animation_name = ProjectSettings.get_setting('dialogic/animations/leave_default', - get_script().resource_path.get_base_dir().path_join('DefaultAnimations/fade_out_down.gd')) + get_script().resource_path.get_base_dir().path_join('DefaultAnimations/fade_down_in_out.gd')) animation_length = _get_leave_default_length() animation_wait = ProjectSettings.get_setting('dialogic/animations/leave_default_wait', true) From 2ce4c9bf2911e2e717d797c4ba9d029d61888255 Mon Sep 17 00:00:00 2001 From: Cake Date: Thu, 18 Apr 2024 09:22:44 +0200 Subject: [PATCH 53/55] Simplify finding fallback name of Character Leave. --- addons/dialogic/Modules/Character/subsystem_portraits.gd | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/addons/dialogic/Modules/Character/subsystem_portraits.gd b/addons/dialogic/Modules/Character/subsystem_portraits.gd index bf419389a..84cceca77 100644 --- a/addons/dialogic/Modules/Character/subsystem_portraits.gd +++ b/addons/dialogic/Modules/Character/subsystem_portraits.gd @@ -522,8 +522,7 @@ func leave_character(character: DialogicCharacter, animation_name:= "", animatio return if animation_name.is_empty(): - animation_name = ProjectSettings.get_setting('dialogic/animations/leave_default', - get_script().resource_path.get_base_dir().path_join('DefaultAnimations/fade_down_in_out.gd')) + animation_name = ProjectSettings.get_setting('dialogic/animations/leave_default', "Fade Down Out") animation_length = _get_leave_default_length() animation_wait = ProjectSettings.get_setting('dialogic/animations/leave_default_wait', true) From 493a0aecb01eec38bb682991fcd013557c4ea458 Mon Sep 17 00:00:00 2001 From: Cake Date: Fri, 19 Apr 2024 08:59:59 +0200 Subject: [PATCH 54/55] Access the correct `DialogicCharacter` node. --- addons/dialogic/Modules/Character/subsystem_portraits.gd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/dialogic/Modules/Character/subsystem_portraits.gd b/addons/dialogic/Modules/Character/subsystem_portraits.gd index 84cceca77..6a3172f99 100644 --- a/addons/dialogic/Modules/Character/subsystem_portraits.gd +++ b/addons/dialogic/Modules/Character/subsystem_portraits.gd @@ -187,7 +187,7 @@ func _change_portrait_mirror(character_node: Node2D, mirrored := false, force := latest_portrait._set_mirror(force or (mirrored != character.mirror != character_node.get_parent().mirrored != current_portrait_info.get('mirror', false))) -func _change_portrait_extradata(character_node:Node2D, extra_data:="") -> void: +func _change_portrait_extradata(character_node: Node2D, extra_data := "") -> void: var latest_portrait := character_node.get_child(-1) if latest_portrait.has_method('_set_extra_data'): @@ -297,7 +297,7 @@ func _change_portrait_z_index(character_node: Node, z_index:int, update_zindex:= ## fully visible yet. func get_character_portrait(character: DialogicCharacter) -> DialogicPortrait: if is_character_joined(character): - var portrait_node: DialogicPortrait = dialogic.current_state_info['portraits'][character.resource_path].node + var portrait_node: DialogicPortrait = dialogic.current_state_info['portraits'][character.resource_path].node.get_child(-1) return portrait_node return null From 19443e9ed214f702986d470def9a2abd62965b71 Mon Sep 17 00:00:00 2001 From: Cake Date: Fri, 19 Apr 2024 11:35:57 +0200 Subject: [PATCH 55/55] Remove older portraits if a new portrait gets instanced. --- .../Modules/Character/subsystem_portraits.gd | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/addons/dialogic/Modules/Character/subsystem_portraits.gd b/addons/dialogic/Modules/Character/subsystem_portraits.gd index 6a3172f99..14b6c9a22 100644 --- a/addons/dialogic/Modules/Character/subsystem_portraits.gd +++ b/addons/dialogic/Modules/Character/subsystem_portraits.gd @@ -117,6 +117,7 @@ func _remove_portrait_timed(portrait_node: Node, animation_path := "Fade In Out" # Changes the portrait of a specific [character node]. func _change_portrait(character_node: Node2D, portrait: String, update_transform := true) -> Dictionary: var character: DialogicCharacter = character_node.get_meta('character') + if portrait.is_empty(): portrait = character.default_portrait @@ -131,22 +132,24 @@ func _change_portrait(character_node: Node2D, portrait: String, update_transform var portrait_node: Node = null var latest_portrait: Node = null + var portrait_count := character_node.get_child_count() - if character_node.get_child_count() > 0: + if portrait_count > 0: latest_portrait = character_node.get_child(-1) # Check if the scene is the same as the currently loaded scene. if (not latest_portrait == null and latest_portrait.get_meta('scene', '') == scene_path and # Also check if the scene supports changing to the given portrait. - (not latest_portrait.has_method('_should_do_portrait_update') - or latest_portrait._should_do_portrait_update(character, portrait))): + latest_portrait._should_do_portrait_update(character, portrait)): portrait_node = latest_portrait info['same_scene'] = true else: + if ResourceLoader.exists(scene_path): var packed_scene: PackedScene = load(scene_path) + if packed_scene: portrait_node = packed_scene.instantiate() else: @@ -166,7 +169,11 @@ func _change_portrait(character_node: Node2D, portrait: String, update_transform if portrait_node.has_method('_update_portrait'): portrait_node._update_portrait(character, portrait) - if !portrait_node.is_inside_tree(): + if not portrait_node.is_inside_tree(): + + if portrait_count > 1: + _remove_portrait(character_node.get_child(0)) + character_node.add_child(portrait_node) if update_transform: @@ -182,7 +189,7 @@ func _change_portrait_mirror(character_node: Node2D, mirrored := false, force := var latest_portrait := character_node.get_child(-1) if latest_portrait.has_method('_set_mirror'): - var character: DialogicCharacter= character_node.get_meta('character') + var character: DialogicCharacter = character_node.get_meta('character') var current_portrait_info := character.get_portrait_info(character_node.get_meta('portrait')) latest_portrait._set_mirror(force or (mirrored != character.mirror != character_node.get_parent().mirrored != current_portrait_info.get('mirror', false)))