From 3d6e6b40350e2a1d9a3e86e754f2af6237269e8d Mon Sep 17 00:00:00 2001 From: oier Date: Thu, 23 Feb 2023 03:56:15 +0100 Subject: [PATCH] custom dialog plugin styled --- .../chooser/simple.gd | 100 +++++++ .../chooser/simple.tscn | 63 +++++ .../esc_dialog_simple.gd | 120 ++++++++ .../plugin.cfg | 7 + .../plugin.gd | 167 ++++++++++++ .../theme.tres | 18 ++ .../types/avatar.gd | 229 ++++++++++++++++ .../types/avatar.tscn | 68 +++++ .../types/floating.gd | 256 ++++++++++++++++++ .../types/floating.tscn | 18 ++ project.godot | 12 +- 11 files changed, 1055 insertions(+), 3 deletions(-) create mode 100644 gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/chooser/simple.gd create mode 100644 gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/chooser/simple.tscn create mode 100644 gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/esc_dialog_simple.gd create mode 100644 gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/plugin.cfg create mode 100644 gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/plugin.gd create mode 100644 gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/theme.tres create mode 100644 gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/types/avatar.gd create mode 100644 gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/types/avatar.tscn create mode 100644 gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/types/floating.gd create mode 100644 gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/types/floating.tscn diff --git a/gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/chooser/simple.gd b/gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/chooser/simple.gd new file mode 100644 index 00000000..278629af --- /dev/null +++ b/gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/chooser/simple.gd @@ -0,0 +1,100 @@ +# A simple dialog chooser that shows selectable lines of text +# Supports timeout and avatar display +extends ESCDialogOptionsChooser + + +export(Color, RGB) var color_normal = Color(1.0,1.0,1.0,1.0) +export(Color, RGB) var color_hover = Color(165.0,42.0,42.0, 1.0) + + +var _no_more_options: bool = false + + +# Hide the chooser at the start just to be safe +func _ready() -> void: + hide_chooser() + pause_mode = PAUSE_MODE_STOP + + +# Process the timeout display +func _process(delta: float) -> void: + if $MarginContainer.visible and self.dialog and self.dialog.timeout > 0: + $TimerProgress.value = ( + self.dialog.timeout - $Timer.time_left + ) / self.dialog.timeout * 100 + + +# Show the chooser +func show_chooser(): + var _vbox = $MarginContainer/ScrollContainer/VBoxContainer + for option_node in _vbox.get_children(): + _vbox.remove_child(option_node) + + _remove_avatar() + + for option in self.dialog.options: + if option.is_valid(): + var _option_node = Button.new() + _option_node.text = (option as ESCDialogOption).option + _option_node.flat = true + _option_node.add_color_override("font_color", color_normal) + _option_node.add_color_override("font_color_hover", color_hover) + _vbox.add_child(_option_node) + _option_node.connect("pressed", self, "_on_answer_selected", [ + option + ]) + + # If we've no options left, signify as much and start the timer with a + # very short interval so the appropriate signal can be fired. Note that + # we have to fire the signal AFTER this method returns as the caller + # is almost certainly yielding after this method returns. + if _vbox.get_child_count() == 0: + _no_more_options = true + $Timer.start(0.05) + return + + if self.dialog.avatar != "-": + $AvatarContainer.add_child( + ResourceLoader.load(self.dialog.avatar).instance() + ) + + $MarginContainer.show() + + if self.dialog.timeout > 0: + $Timer.start(self.dialog.timeout) + + +# Hide the chooser +func hide_chooser(): + $MarginContainer.hide() + + +# An option was choosen, emit the option +# +# #### Parameters +# - option: Option that was chosen +func _option_chosen(option: ESCDialogOption): + _remove_avatar() + $TimerProgress.value = 0 + emit_signal("option_chosen", option) + + +# An option was chosen directly from the list +# +# #### Parameters +# - option: Option that was chosen +func _on_answer_selected(option: ESCDialogOption): + _option_chosen(option) + + +# The timeout came and a option was selected +func _on_Timer_timeout() -> void: + var option_chosen = null if _no_more_options else self.dialog.options[self.dialog.timeout_option - 1] + _no_more_options = false + _option_chosen(option_chosen) + + +# Remove the avatar +func _remove_avatar(): + if $AvatarContainer.get_child_count() > 0: + $AvatarContainer.remove_child($AvatarContainer.get_child(0)) diff --git a/gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/chooser/simple.tscn b/gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/chooser/simple.tscn new file mode 100644 index 00000000..fe3e0a12 --- /dev/null +++ b/gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/chooser/simple.tscn @@ -0,0 +1,63 @@ +[gd_scene load_steps=4 format=2] + +[ext_resource path="res://addons/escoria-dialog-simple/chooser/simple.gd" type="Script" id=1] + +[sub_resource type="Gradient" id=1] +colors = PoolColorArray( 1, 0, 0, 1, 1, 0, 0, 1 ) + +[sub_resource type="GradientTexture" id=2] +gradient = SubResource( 1 ) + +[node name="text_dialog_choice" type="Control"] +anchor_right = 1.0 +anchor_bottom = 1.0 +script = ExtResource( 1 ) + +[node name="MarginContainer" type="MarginContainer" parent="."] +margin_left = 20.0 +margin_top = 20.0 +margin_right = 1280.0 +margin_bottom = 900.0 +mouse_filter = 2 +custom_constants/margin_top = 20 +custom_constants/margin_left = 20 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer"] +margin_left = 20.0 +margin_top = 20.0 +margin_right = 1260.0 +margin_bottom = 880.0 +scroll_horizontal_enabled = false +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/ScrollContainer"] +margin_right = 1240.0 +margin_bottom = 860.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +custom_constants/separation = 20 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Timer" type="Timer" parent="."] +one_shot = true + +[node name="TimerProgress" type="TextureProgress" parent="."] +anchor_right = 1.0 +rect_min_size = Vector2( 0, 20 ) +texture_progress = SubResource( 2 ) +nine_patch_stretch = true +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="AvatarContainer" type="Node2D" parent="."] +position = Vector2( 94, 68 ) + +[connection signal="timeout" from="Timer" to="." method="_on_Timer_timeout"] diff --git a/gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/esc_dialog_simple.gd b/gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/esc_dialog_simple.gd new file mode 100644 index 00000000..2917eb63 --- /dev/null +++ b/gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/esc_dialog_simple.gd @@ -0,0 +1,120 @@ +# A simple dialog manager for Escoria +extends ESCDialogManager +class_name ESCReturnToMonekyIslandDialogs + + +# The currently running player +var _type_player: Node = null + +# Reference to the dialog player +var _dialog_player: Node = null + + +# Check whether a specific type is supported by the +# dialog plugin +# +# #### Parameters +# - type: required type +# *Returns* Whether the type is supported or not +func has_type(type: String) -> bool: + return true if type in ["floating", "avatar"] else false + + +# Check whether a specific chooser type is supported by the +# dialog plugin +# +# #### Parameters +# - type: required chooser type +# *Returns* Whether the type is supported or not +func has_chooser_type(type: String) -> bool: + return true if type == "simple" else false + + +# Output a text said by the item specified by the global id. Emit +# `say_finished` after finishing displaying the text. +# +# #### Parameters +# - dialog_player: Node of the dialog player in the UI +# - global_id: Global id of the item that is speaking +# - text: Text to say, optional prefixed by a translation key separated +# by a ":" +# - type: Type of dialog box to use +func say(dialog_player: Node, global_id: String, text: String, type: String): + _dialog_player = dialog_player + + if type == "floating": + _type_player = preload(\ + "res://gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/types/floating.tscn"\ + ).instance() + else: + _type_player = preload(\ + "res://gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/types/avatar.tscn"\ + ).instance() + + _type_player.connect("say_finished", self, "_on_say_finished", [], CONNECT_ONESHOT) + _type_player.connect("say_visible", self, "_on_say_visible", [], CONNECT_ONESHOT) + + _dialog_player.add_child(_type_player) + _type_player.say(global_id, text) +# yield(_type_player, "say_finished") +# if _dialog_player.get_children().has(_type_player): +# _dialog_player.remove_child(_type_player) +# emit_signal("say_finished") + + +func _on_say_finished(): + if _dialog_player.get_children().has(_type_player): + _dialog_player.remove_child(_type_player) + emit_signal("say_finished") + + +func _on_say_visible(): + emit_signal("say_visible") + + +# Present an option chooser to the player and sends the signal +# `option_chosen` with the chosen dialog option +# +# #### Parameters +# - dialog_player: Node of the dialog player in the UI +# - dialog: Information about the dialog to display +func choose(dialog_player: Node, dialog: ESCDialog): + var chooser = preload(\ + "res://gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/chooser/simple.tscn"\ + ).instance() + dialog_player.add_child(chooser) + chooser.set_dialog(dialog) + chooser.show_chooser() + var option = yield(chooser, "option_chosen") + dialog_player.remove_child(chooser) + emit_signal("option_chosen", option) + + +# Trigger running the dialogue faster +func speedup(): + if _type_player != null: + _type_player.speedup() + + +# Trigger an instant finish of the current dialog +func finish(): + if _type_player != null: + _type_player.finish() + + +# The say command has been interrupted, cancel the dialog display +func interrupt(): + if _dialog_player.get_children().has(_type_player): + ( + escoria.object_manager.get_object(escoria.object_manager.SPEECH).node\ + as ESCSpeechPlayer + ).set_state("off") + + _dialog_player.remove_child(_type_player) + emit_signal("say_finished") + + +# To be called if voice audio has finished. +func voice_audio_finished(): + if _type_player != null: + _type_player.voice_audio_finished() diff --git a/gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/plugin.cfg b/gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/plugin.cfg new file mode 100644 index 00000000..02ea1384 --- /dev/null +++ b/gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="Escoria Return to Monkey Island UI Dialog Simple" +description="Return to Monkey Island like UI for the Escoria Framework (Dialogs)" +author="Arkitekt" +version="0.1.0" +script="plugin.gd" diff --git a/gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/plugin.gd b/gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/plugin.gd new file mode 100644 index 00000000..5f052ee6 --- /dev/null +++ b/gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/plugin.gd @@ -0,0 +1,167 @@ +# A simple dialog manager for Escoria +tool +extends EditorPlugin +class_name SimpleDialogPlugin + + +const MANAGER_CLASS="res://gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/esc_dialog_simple.gd" +const SETTINGS_ROOT="escoria/dialog_simple" + +const AVATARS_PATH = "%s/avatars_path" % SETTINGS_ROOT +const TEXT_TIME_PER_LETTER_MS = "%s/text_time_per_letter_ms" % SETTINGS_ROOT +const TEXT_TIME_PER_LETTER_MS_FAST = "%s/text_time_per_fast_letter_ms" % SETTINGS_ROOT +const READING_SPEED_IN_WPM = "%s/reading_speed_in_wpm" % SETTINGS_ROOT +const CLEAR_TEXT_BY_CLICK_ONLY = "%s/clear_text_by_click_only" % SETTINGS_ROOT +const LEFT_CLICK_ACTION = "%s/left_click_action" % SETTINGS_ROOT +const STOP_TALKING_ANIMATION_ON = "%s/stop_talking_animation_on" % SETTINGS_ROOT + +const LEFT_CLICK_ACTION_SPEED_UP = "Speed up" +const LEFT_CLICK_ACTION_INSTANT_FINISH = "Instant finish" +const LEFT_CLICK_ACTION_NOTHING = "None" + +const STOP_TALKING_ANIMATION_ON_END_OF_TEXT = "End of text" +const STOP_TALKING_ANIMATION_ON_END_OF_AUDIO = "End of audio" + +const READING_SPEED_IN_WPM_DEFAULT_VALUE = 200 +const TEXT_TIME_PER_LETTER_MS_DEFAULT_VALUE = 100 +const TEXT_TIME_PER_LETTER_MS_FAST_DEFAULT_VALUE = 25 + + +var left_click_actions: PoolStringArray = [ + LEFT_CLICK_ACTION_SPEED_UP, + LEFT_CLICK_ACTION_INSTANT_FINISH, + LEFT_CLICK_ACTION_NOTHING +] + +var stop_talking_animation_on_options: PoolStringArray = [ + STOP_TALKING_ANIMATION_ON_END_OF_TEXT, + STOP_TALKING_ANIMATION_ON_END_OF_AUDIO +] + + +# Override function to return the plugin name. +func get_plugin_name(): + return "escoria-dialog-simple" + + +# Unregister ourselves +func disable_plugin(): + print("Disabling plugin Escoria Dialog Simple") + ESCProjectSettingsManager.remove_setting( + ESCProjectSettingsManager.DEFAULT_DIALOG_TYPE + ) + + ESCProjectSettingsManager.remove_setting( + AVATARS_PATH + ) + + ESCProjectSettingsManager.remove_setting( + TEXT_TIME_PER_LETTER_MS + ) + + ESCProjectSettingsManager.remove_setting( + TEXT_TIME_PER_LETTER_MS_FAST + ) + + ESCProjectSettingsManager.remove_setting( + CLEAR_TEXT_BY_CLICK_ONLY + ) + + ESCProjectSettingsManager.remove_setting( + READING_SPEED_IN_WPM + ) + + ESCProjectSettingsManager.remove_setting( + LEFT_CLICK_ACTION + ) + + ESCProjectSettingsManager.remove_setting( + STOP_TALKING_ANIMATION_ON + ) + + EscoriaPlugin.deregister_dialog_manager(MANAGER_CLASS) + + +# Add ourselves to the list of dialog managers +func enable_plugin(): + print("Enabling plugin Escoria Dialog Simple") + + if EscoriaPlugin.register_dialog_manager(self, MANAGER_CLASS): + ESCProjectSettingsManager.register_setting( + ESCProjectSettingsManager.DEFAULT_DIALOG_TYPE, + "floating", + { + "type": TYPE_STRING + } + ) + + ESCProjectSettingsManager.register_setting( + AVATARS_PATH, + "res://game/dialog_avatars", + { + "type": TYPE_STRING, + "hint": PROPERTY_HINT_DIR + } + ) + + ESCProjectSettingsManager.register_setting( + TEXT_TIME_PER_LETTER_MS, + TEXT_TIME_PER_LETTER_MS_DEFAULT_VALUE, + { + "type": TYPE_REAL + } + ) + + ESCProjectSettingsManager.register_setting( + TEXT_TIME_PER_LETTER_MS_FAST, + TEXT_TIME_PER_LETTER_MS_FAST_DEFAULT_VALUE, + { + "type": TYPE_REAL + } + ) + + ESCProjectSettingsManager.register_setting( + CLEAR_TEXT_BY_CLICK_ONLY, + false, + { + "type": TYPE_BOOL + } + ) + + ESCProjectSettingsManager.register_setting( + READING_SPEED_IN_WPM, + READING_SPEED_IN_WPM_DEFAULT_VALUE, + { + "type": TYPE_INT + } + ) + + var left_click_actions_string: String = left_click_actions.join(",") + + ESCProjectSettingsManager.register_setting( + LEFT_CLICK_ACTION, + LEFT_CLICK_ACTION_SPEED_UP, + { + "type": TYPE_STRING, + "hint": PROPERTY_HINT_ENUM, + "hint_string": left_click_actions_string + } + ) + + var stop_talking_animation_on_options_string: String = stop_talking_animation_on_options.join(",") + + ESCProjectSettingsManager.register_setting( + STOP_TALKING_ANIMATION_ON, + STOP_TALKING_ANIMATION_ON_END_OF_AUDIO, + { + "type": TYPE_STRING, + "hint": PROPERTY_HINT_ENUM, + "hint_string": stop_talking_animation_on_options_string + } + ) + + else: + get_editor_interface().set_plugin_enabled( + get_plugin_name(), + false + ) diff --git a/gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/theme.tres b/gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/theme.tres new file mode 100644 index 00000000..3dddbd81 --- /dev/null +++ b/gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/theme.tres @@ -0,0 +1,18 @@ +[gd_resource type="Theme" load_steps=3 format=2] + +[ext_resource path="res://gymkhana/addons/escoria-ui-return-monkey-island/fonts/caslonantique.tres" type="DynamicFont" id=1] + +[sub_resource type="StyleBoxFlat" id=1] +content_margin_left = 5.0 +content_margin_right = 5.0 +content_margin_top = 5.0 +content_margin_bottom = 5.0 +bg_color = Color( 0, 0, 0, 0.25098 ) +corner_radius_top_left = 8 +corner_radius_top_right = 8 +corner_radius_bottom_right = 8 +corner_radius_bottom_left = 8 + +[resource] +default_font = ExtResource( 1 ) +RichTextLabel/styles/normal = SubResource( 1 ) diff --git a/gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/types/avatar.gd b/gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/types/avatar.gd new file mode 100644 index 00000000..ebd87ef5 --- /dev/null +++ b/gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/types/avatar.gd @@ -0,0 +1,229 @@ +# A dialog GUI showing a dialog box and character portraits +extends Popup + + +# Signal emitted when text has been said +signal say_finished + +# Signal emitted when text has just become fully visible +signal say_visible + + +# The text speed per character for normal display +var _text_time_per_character: float + +# The text speed per character if the dialog line is skipped +var _fast_text_time_per_character: float + +# The reading speed to be used in determining the length of time text remains +# on the screen. +var _reading_speed_in_wpm: int + +# Used to extract words from lines of text. +var _word_regex: RegEx = RegEx.new() + +# Whether the current dialog is speeding up +var _is_speeding_up: bool = false + +# The current line of text being displayed. +var _current_line: String + + +# The node holding the avatar +onready var avatar_node = $Panel/MarginContainer/HSplitContainer/VBoxContainer\ + /avatar + +# The node showing the text +onready var text_node = $Panel/MarginContainer/HSplitContainer/text + +# The tween node for text animations +onready var tween = $Panel/MarginContainer/HSplitContainer/text/Tween + +# Whether the dialog manager is paused +onready var is_paused: bool = true + + + +# Build up the UI +func _ready(): + _text_time_per_character = ProjectSettings.get_setting( + SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS + ) + + if _text_time_per_character < 0: + escoria.logger.warn( + self, + "%s setting must be a non-negative number. Will use default value of %s." % + [ + SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS, + SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS_DEFAULT_VALUE + ] + ) + + _text_time_per_character = SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS_DEFAULT_VALUE + + _fast_text_time_per_character = ProjectSettings.get_setting( + SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS_FAST + ) + + if _fast_text_time_per_character < 0: + escoria.logger.warn( + self, + "%s setting must be a non-negative number. Will use default value of %s." % + [ + SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS_FAST, + SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS_FAST_DEFAULT_VALUE + ] + ) + + _fast_text_time_per_character = SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS_FAST_DEFAULT_VALUE + + _reading_speed_in_wpm = ProjectSettings.get_setting( + SimpleDialogPlugin.READING_SPEED_IN_WPM + ) + + if _reading_speed_in_wpm <= 0: + escoria.logger.warn( + self, + "%s setting must be a positive number. Will use default value of %s." % + [ + SimpleDialogPlugin.READING_SPEED_IN_WPM, + SimpleDialogPlugin.READING_SPEED_IN_WPM_DEFAULT_VALUE + ] + ) + + _reading_speed_in_wpm = SimpleDialogPlugin.READING_SPEED_IN_WPM_DEFAULT_VALUE + + _word_regex.compile("\\S+") + + text_node.bbcode_enabled = true + tween.connect( + "tween_completed", + self, + "_on_dialog_line_typed" + ) + + escoria.connect("paused", self, "_on_paused") + escoria.connect("resumed", self, "_on_resumed") + + +# Switch the current character +# +# #### Parameters +# - name: The name of the current character +func set_current_character(name: String): + if ProjectSettings.get_setting("escoria/dialog_simple/avatars_path").empty(): + escoria.logger.warn(self, "Unable to load avatar '%s': Avatar path not specified" % name) + return + + var avatar = "%s/%s.tres" % [ + ProjectSettings.get_setting("escoria/dialog_simple/avatars_path"), + name + ] + if ResourceLoader.exists(avatar): + avatar_node.texture = ResourceLoader.load(avatar) + + if avatar_node.texture is AnimatedTexture: + avatar_node.texture.current_frame = 0 + avatar_node.texture.pause = false + else: + escoria.logger.warn(self, "Unable to load avatar '%s': Resource not found in path '%s'" % + [name, ProjectSettings.get_setting("escoria/dialog_simple/avatars_path")]) + + +# Make a character say something +# +# #### Parameters +# - character: The global id of the character speaking +# - line: Line to say +func say(character: String, line: String): + _current_line = line + + _is_speeding_up = false + popup_centered() + set_current_character(character) + + text_node.bbcode_text = tr(line) + + text_node.percent_visible = 0.0 + var time_show_full_text = _text_time_per_character / 1000 * len(line) + + tween.interpolate_property(text_node, "percent_visible", + 0.0, 1.0, time_show_full_text, + Tween.TRANS_LINEAR, Tween.EASE_IN_OUT) + tween.start() + + +# Called by the dialog player when the +func speedup(): + if not _is_speeding_up: + _is_speeding_up = true + var time_show_full_text = _fast_text_time_per_character / 1000 * len(_current_line) + tween.remove_all() + tween.interpolate_property(text_node, "percent_visible", + text_node.percent_visible, 1.0, time_show_full_text, + Tween.TRANS_LINEAR, Tween.EASE_IN_OUT) + tween.start() + + +# Called by the dialog player when user wants to finish dialogue immediately. +func finish(): + tween.remove_all() + tween.interpolate_property(text_node, "percent_visible", + text_node.percent_visible, 1.0, 0.0) + tween.start() + + +# To be called if voice audio has finished. +func voice_audio_finished(): + if avatar_node and avatar_node.texture: + avatar_node.texture.current_frame = 0 + avatar_node.texture.pause = true + + +# The dialog line was printed, start the waiting time and then finish +# the dialog +func _on_dialog_line_typed(object, key): + if avatar_node.texture is AnimatedTexture: + avatar_node.texture.current_frame = 0 + avatar_node.texture.pause = true + + text_node.visible_characters = -1 + + var time_to_disappear: float = _calculate_time_to_disappear() + $Timer.start(time_to_disappear) + $Timer.connect("timeout", self, "_on_dialog_finished") + + emit_signal("say_visible") + + +func _calculate_time_to_disappear() -> float: + return (_get_number_of_words() / _reading_speed_in_wpm as float) * 60 + + +func _get_number_of_words() -> int: + return _word_regex.search_all(text_node.get_text()).size() + + +# Ending the dialog +func _on_dialog_finished(): + # Only trigger to clear the text if we aren't limiting the clearing trigger to a click. + if not ESCProjectSettingsManager.get_setting(SimpleDialogPlugin.CLEAR_TEXT_BY_CLICK_ONLY): + emit_signal("say_finished") + queue_free() + + +# Handler managing pause notification from Escoria +func _on_paused(): + if tween.is_active(): + is_paused = true + tween.stop_all() + + +# Handler managing resume notification from Escoria +func _on_resumed(): + if not tween.is_active(): + is_paused = false + tween.resume_all() + + diff --git a/gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/types/avatar.tscn b/gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/types/avatar.tscn new file mode 100644 index 00000000..282ea329 --- /dev/null +++ b/gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/types/avatar.tscn @@ -0,0 +1,68 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://addons/escoria-dialog-simple/types/avatar.gd" type="Script" id=1] + +[node name="dialog_box" type="Popup"] +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_right = -782.0 +margin_bottom = -734.0 +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Timer" type="Timer" parent="."] + +[node name="Panel" type="Panel" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="MarginContainer" type="MarginContainer" parent="Panel"] +anchor_right = 1.0 +anchor_bottom = 1.0 +custom_constants/margin_right = 20 +custom_constants/margin_top = 20 +custom_constants/margin_left = 20 +custom_constants/margin_bottom = 20 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="HSplitContainer" type="HSplitContainer" parent="Panel/MarginContainer"] +margin_left = 20.0 +margin_top = 20.0 +margin_right = 478.0 +margin_bottom = 146.0 +custom_constants/separation = 35 +dragger_visibility = 1 + +[node name="VBoxContainer" type="VBoxContainer" parent="Panel/MarginContainer/HSplitContainer"] +margin_right = 88.0 +margin_bottom = 126.0 +size_flags_horizontal = 3 +size_flags_stretch_ratio = 0.3 + +[node name="avatar" type="TextureRect" parent="Panel/MarginContainer/HSplitContainer/VBoxContainer"] +margin_right = 88.0 +margin_bottom = 108.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +expand = true + +[node name="text" type="RichTextLabel" parent="Panel/MarginContainer/HSplitContainer"] +margin_left = 123.0 +margin_right = 458.0 +margin_bottom = 126.0 +size_flags_horizontal = 3 +bbcode_enabled = true +bbcode_text = "Here be some text" +text = "Here be some text" +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Tween" type="Tween" parent="Panel/MarginContainer/HSplitContainer/text"] diff --git a/gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/types/floating.gd b/gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/types/floating.gd new file mode 100644 index 00000000..cd00d1c4 --- /dev/null +++ b/gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/types/floating.gd @@ -0,0 +1,256 @@ +# A dialog UI using a label above the head of the character +extends RichTextLabel + + +# Signal emitted when text has been said +signal say_finished + +# Signal emitted when text has just become fully visible +signal say_visible + + +# The text speed per character for normal display +var _text_time_per_character: float + +# The text speed per character if the dialog line is skipped +var _fast_text_time_per_character: float + +# The reading speed to be used in determining the length of time text remains +# on the screen. +var _reading_speed_in_wpm: int + +# Used to extract words from lines of text. +var _word_regex: RegEx = RegEx.new() + + +# Current character speaking, to keep track of reference for animation purposes +var _current_character + +# Whether the current dialog is speeding up +var _is_speeding_up: bool = false + +# The current line of text being displayed. +var _current_line: String + + +# Tween node for text animation +onready var tween: Tween = $Tween + +# The node showing the text +onready var text_node: RichTextLabel = self + +# Whether the dialog manager is paused +onready var is_paused: bool = true + + +# Enable bbcode and catch the signal when a tween completed +func _ready(): + _text_time_per_character = ProjectSettings.get_setting( + SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS + ) + + if _text_time_per_character < 0: + escoria.logger.warn( + self, + "%s setting must be a non-negative number. Will use default value of %s." % + [ + SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS, + SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS_DEFAULT_VALUE + ] + ) + + _text_time_per_character = SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS_DEFAULT_VALUE + + _fast_text_time_per_character = ProjectSettings.get_setting( + SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS_FAST + ) + + if _fast_text_time_per_character < 0: + escoria.logger.warn( + self, + "%s setting must be a non-negative number. Will use default value of %s." % + [ + SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS_FAST, + SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS_FAST_DEFAULT_VALUE + ] + ) + + _fast_text_time_per_character = SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS_FAST_DEFAULT_VALUE + + _reading_speed_in_wpm = ProjectSettings.get_setting( + SimpleDialogPlugin.READING_SPEED_IN_WPM + ) + + if _reading_speed_in_wpm <= 0: + escoria.logger.warn( + self, + "%s setting must be a positive number. Will use default value of %s." % + [ + SimpleDialogPlugin.READING_SPEED_IN_WPM, + SimpleDialogPlugin.READING_SPEED_IN_WPM_DEFAULT_VALUE + ] + ) + + _reading_speed_in_wpm = SimpleDialogPlugin.READING_SPEED_IN_WPM_DEFAULT_VALUE + + _word_regex.compile("\\S+") + + bbcode_enabled = true + $Tween.connect("tween_completed", self, "_on_dialog_line_typed") + + connect("tree_exiting", self, "_on_tree_exiting") + + escoria.connect("paused", self, "_on_paused") + escoria.connect("resumed", self, "_on_resumed") + + _current_line = "" + + +func _process(delta): + if _current_character.is_inside_tree() and \ + _current_character.has_node("dialog_position"): + # Position the RichTextLabel on the character's dialog position, if any. + rect_position = _current_character.get_node("dialog_position") \ + .get_global_transform_with_canvas().origin + rect_position.x -= rect_size.x / 2 + + if rect_position.x < 0: + rect_position.x = 0 + + var screen_margin = rect_position.x + rect_size.x - \ + ProjectSettings.get("display/window/size/width") + + if screen_margin > 0: + rect_position.x -= screen_margin + + +# Make a character say something +# +# #### Parameters +# - character: The global id of the character speaking +# - line: Line to say +func say(character: String, line: String) : + _current_line = line + + show() + + _is_speeding_up = false + + # Position the RichTextLabel on the character's dialog position, if any. + _current_character = escoria.object_manager.get_object(character).node + + # Set text color to color set in the actor + var text_color = _current_character.dialog_color + var text_color_html = text_color.to_html(false) + + text_node.bbcode_text = "[center][color=#" + text_color_html + "]" \ + .format([text_color_html]) + tr(line) + "[/color][center]" + + if _current_character.is_inside_tree() and \ + _current_character.has_node("dialog_position"): + rect_position = _current_character.get_node( + "dialog_position" + ).get_global_transform_with_canvas().origin + rect_position.x -= rect_size.x / 2 + else: + rect_position.x = 0 + rect_size.x = ProjectSettings.get_setting("display/window/size/width") + + if rect_position.x < 0: + rect_position.x = 0 + + var screen_margin = rect_position.x + rect_size.x - \ + ProjectSettings.get("display/window/size/width") + + if screen_margin > 0: + rect_position.x -= screen_margin + + _current_character.start_talking() + + text_node.percent_visible = 0.0 + var time_show_full_text = _text_time_per_character / 1000 * len(_current_line) + + tween.interpolate_property(text_node, "percent_visible", + 0.0, 1.0, time_show_full_text, + Tween.TRANS_LINEAR, Tween.EASE_IN_OUT) + tween.start() + set_process(true) + + +# Called by the dialog player when user wants to finish dialogue fast. +func speedup(): + if not _is_speeding_up: + _is_speeding_up = true + var time_show_full_text = _fast_text_time_per_character / 1000 * len(_current_line) + + tween.remove_all() + tween.interpolate_property(text_node, "percent_visible", + text_node.percent_visible, 1.0, time_show_full_text, + Tween.TRANS_LINEAR, Tween.EASE_IN_OUT) + tween.start() + + +# Called by the dialog player when user wants to finish dialogue immediately. +func finish(): + tween.remove_all() + tween.interpolate_property(text_node, "percent_visible", + text_node.percent_visible, 1.0, 0.0) + tween.start() + + +# To be called if voice audio has finished. +func voice_audio_finished(): + _stop_character_talking() + + +# The dialog line was printed, start the waiting time and then finish +# the dialog +func _on_dialog_line_typed(object, key): + _stop_character_talking() + text_node.visible_characters = -1 + + var time_to_disappear: float = _calculate_time_to_disappear() + $Timer.start(time_to_disappear) + $Timer.connect("timeout", self, "_on_dialog_finished") + + emit_signal("say_visible") + + +func _calculate_time_to_disappear() -> float: + return (_get_number_of_words() / _reading_speed_in_wpm as float) * 60 + + +func _get_number_of_words() -> int: + return _word_regex.search_all(text_node.get_text()).size() + + +# Ending the dialog +func _on_dialog_finished(): + # Only trigger to clear the text if we aren't limiting the clearing trigger to a click. + if not ESCProjectSettingsManager.get_setting(SimpleDialogPlugin.CLEAR_TEXT_BY_CLICK_ONLY): + emit_signal("say_finished") + + +# Handler managing pause notification from Escoria +func _on_paused(): + if tween.is_active(): + is_paused = true + tween.stop_all() + + +# Handler managing resume notification from Escoria +func _on_resumed(): + if not tween.is_active(): + is_paused = false + tween.resume_all() + + + # Handler to deal with this node being removed +func _on_tree_exiting() -> void: + _stop_character_talking() + + +func _stop_character_talking(): + # Make the speaking item animation stop talking, if it is still alive + if is_instance_valid(_current_character) and _current_character != null: + _current_character.stop_talking() diff --git a/gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/types/floating.tscn b/gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/types/floating.tscn new file mode 100644 index 00000000..e670b198 --- /dev/null +++ b/gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/types/floating.tscn @@ -0,0 +1,18 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://addons/escoria-dialog-simple/types/floating.gd" type="Script" id=1] +[ext_resource path="res://gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/theme.tres" type="Theme" id=2] + +[node name="dialog_label" type="RichTextLabel"] +margin_right = 672.0 +margin_bottom = 97.0 +theme = ExtResource( 2 ) +bbcode_enabled = true +bbcode_text = "[center]Here be some text.[/center]" +text = "Here be some text." +fit_content_height = true +script = ExtResource( 1 ) + +[node name="Tween" type="Tween" parent="."] + +[node name="Timer" type="Timer" parent="."] diff --git a/project.godot b/project.godot index 0365cc65..c66ba6f5 100644 --- a/project.godot +++ b/project.godot @@ -384,6 +384,11 @@ _global_script_classes=[ { "language": "GDScript", "path": "res://addons/escoria-core/game/core-scripts/resources/esc_resource_descriptor.gd" }, { +"base": "ESCDialogManager", +"class": "ESCReturnToMonekyIslandDialogs", +"language": "GDScript", +"path": "res://gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/esc_dialog_simple.gd" +}, { "base": "Node2D", "class": "ESCRoom", "language": "GDScript", @@ -607,7 +612,7 @@ _global_script_classes=[ { "base": "EditorPlugin", "class": "SimpleDialogPlugin", "language": "GDScript", -"path": "res://addons/escoria-dialog-simple/plugin.gd" +"path": "res://gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/plugin.gd" }, { "base": "SlideCommand", "class": "SlideBlockCommand", @@ -765,6 +770,7 @@ _global_script_class_icons={ "ESCProjectSettingsManager": "", "ESCResourceCache": "", "ESCResourceDescriptor": "", +"ESCReturnToMonekyIslandDialogs": "", "ESCRoom": "res://addons/escoria-core/design/esc_room.svg", "ESCRoomManager": "", "ESCRoomObjects": "", @@ -888,7 +894,7 @@ ui/inventory_items_path="res://gymkhana/items/inventory" ui/default_transition="instant" ui/transition_paths=[ "res://addons/escoria-core/game/scenes/transitions/shaders/" ] ui/inventory_item_size=Vector2( 72, 72 ) -ui/dialog_managers=[ "res://addons/escoria-dialog-simple/esc_dialog_simple.gd" ] +ui/dialog_managers=[ "res://gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/esc_dialog_simple.gd" ] sound/master_volume=1 sound/music_volume=1 sound/sfx_volume=1 @@ -900,7 +906,7 @@ platform/skip_cache.mobile=true sound/speech_enabled=1 ui/tooltip_follows_mouse=false main/escoria_version="" -ui/dialogs_chooser="res://addons/escoria-core/ui_library/dialogs/text_dialog_chooser.tscn" +ui/dialogs_chooser="res://gymkhana/addons/escoria-ui-return-monkey-island-dialog-simple/text_dialog_chooser.tscn" ui/default_dialog_scene="res://addons/escoria-core/ui_library/dialogs/floating_dialog_player.tscn" main/action_default_script="res://action_defaults.esc" dialog_simple/text_speed_per_character=0.1