feat: ported escoria-ui-return-monkey-island-dialog-simple

This commit is contained in:
2025-01-20 23:30:12 +01:00
parent f34f96e87f
commit a8c527c3e4
18 changed files with 232 additions and 296 deletions

View File

@@ -42,9 +42,8 @@ func show_chooser():
_option_node.add_theme_color_override("font_color", color_normal) _option_node.add_theme_color_override("font_color", color_normal)
_option_node.add_theme_color_override("font_color_hover", color_hover) _option_node.add_theme_color_override("font_color_hover", color_hover)
_vbox.add_child(_option_node) _vbox.add_child(_option_node)
_option_node.connect("pressed", self, "_on_answer_selected", [
option _option_node.pressed.connect(_on_answer_selected.bind(option))
])
# If we've no options left, signify as much and start the timer with a # 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 # very short interval so the appropriate signal can be fired. Note that
@@ -78,7 +77,7 @@ func hide_chooser():
func _option_chosen(option: ESCDialogOption): func _option_chosen(option: ESCDialogOption):
_remove_avatar() _remove_avatar()
$TimerProgress.value = 0 $TimerProgress.value = 0
emit_signal("option_chosen", option) option_chosen.emit(option)
# An option was chosen directly from the list # An option was chosen directly from the list

View File

@@ -42,7 +42,8 @@ func run(_command_params: Array) -> int:
escoria.dialog_player.say( escoria.dialog_player.say(
speaking_character_global_id, speaking_character_global_id,
"", "",
text text,
text # TODO (Eneko): should we pass real key?
) )
await escoria.dialog_player.say_finished await escoria.dialog_player.say_finished
escoria.current_state = escoria.GAME_STATE.DEFAULT escoria.current_state = escoria.GAME_STATE.DEFAULT

View File

@@ -94,7 +94,7 @@ func run(command_params: Array) -> int:
escoria.current_state = escoria.GAME_STATE.DEFAULT escoria.current_state = escoria.GAME_STATE.DEFAULT
return ESCExecution.RC_CANCEL return ESCExecution.RC_CANCEL
var text = param_list_id + "_" + String(rng.randi_range(0,param_lenght -1)) + ':" "' var text = param_list_id + "_" + String.num_int64(rng.randi_range(0,param_lenght -1)) + ':" "'
var speaking_character_global_id = escoria.main.current_scene.player.global_id \ var speaking_character_global_id = escoria.main.current_scene.player.global_id \
if param_global_id.to_upper() == CURRENT_PLAYER_KEYWORD \ if param_global_id.to_upper() == CURRENT_PLAYER_KEYWORD \
@@ -103,7 +103,8 @@ func run(command_params: Array) -> int:
escoria.dialog_player.say( escoria.dialog_player.say(
speaking_character_global_id, speaking_character_global_id,
"", "",
text text,
text # TODO (Eneko): should we pass real key?
) )
await escoria.dialog_player.say_finished await escoria.dialog_player.say_finished
escoria.current_state = escoria.GAME_STATE.DEFAULT escoria.current_state = escoria.GAME_STATE.DEFAULT

View File

@@ -107,7 +107,8 @@ func run(command_params: Array) -> int:
escoria.dialog_player.say( escoria.dialog_player.say(
speaking_character_global_id, speaking_character_global_id,
"", "",
text text,
text # TODO (Eneko): should we pass real key?
) )
await escoria.dialog_player.say_finished await escoria.dialog_player.say_finished
escoria.current_state = escoria.GAME_STATE.DEFAULT escoria.current_state = escoria.GAME_STATE.DEFAULT

View File

@@ -16,7 +16,7 @@ the node that inherits from this state machine interface
If you don't the game will crash (on purpose, so you won't If you don't the game will crash (on purpose, so you won't
forget to initialize the state machine) forget to initialize the state machine)
""" """
@export var START_STATE: NodePath @export var START_STATE: Node
var states_map = {} var states_map = {}
var states_stack = [] # can also be used as a pushdown automaton var states_stack = [] # can also be used as a pushdown automaton
@@ -27,7 +27,7 @@ var _active = false: set = set_active
func initialize(start_state): func initialize(start_state):
for child in get_children(): for child in get_children():
child.connect("finished", Callable(self, "_change_state")) child.finished.connect(_change_state)
set_active(true) set_active(true)
states_stack.push_front(start_state) states_stack.push_front(start_state)
@@ -76,7 +76,7 @@ func _change_state(state_name):
current_state = states_stack[0] current_state = states_stack[0]
emit_signal("state_changed", current_state) state_changed.emit(current_state)
#if state_name != "previous": #if state_name != "previous":
current_state.enter() current_state.enter()

View File

@@ -64,7 +64,7 @@ func _disable_plugin():
# Add ourselves to the list of dialog managers # Add ourselves to the list of dialog managers
func enable_plugin(): func _enable_plugin():
print("Enabling plugin Escoria Dialog Simple") print("Enabling plugin Escoria Dialog Simple")
if EscoriaPlugin.register_dialog_manager(self, MANAGER_CLASS): if EscoriaPlugin.register_dialog_manager(self, MANAGER_CLASS):

View File

@@ -29,7 +29,6 @@ func _ready() -> void:
escoria.connect("paused", Callable(self, "_on_paused")) escoria.connect("paused", Callable(self, "_on_paused"))
# Check whether a specific type is supported by the # Check whether a specific type is supported by the
# dialog plugin # dialog plugin
# #
@@ -40,25 +39,6 @@ func has_type(type: String) -> bool:
return true if type in ["floating"] else false return true if type in ["floating"] 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
# Instructs the dialog manager to preserve the next dialog box used by a `say`
# command until a call to `disable_preserve_dialog_box` is made.
#
# This method should be idempotent, i.e. if called after the first time and
# prior to `disable_preserve_dialog_box` being called, the result should be the
# same.
func enable_preserve_dialog_box() -> void:
_should_preserve_dialog_box = true
# Instructs the dialog manager to no longer preserve the currently-preserved # Instructs the dialog manager to no longer preserve the currently-preserved
# dialog box or to not preserve the next dialog box used by a `say` command # dialog box or to not preserve the next dialog box used by a `say` command
# (this is the default state). # (this is the default state).
@@ -74,36 +54,6 @@ func disable_preserve_dialog_box() -> void:
_preserved_type_player_type = "" _preserved_type_player_type = ""
_dialog_player.remove_child(_dialog_tip) _dialog_player.remove_child(_dialog_tip)
# 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
_initialize_say_states(global_id, text, type)
if _should_preserve_dialog_box:
# If the dialog box type doesn't match what's currently being reused (if anything),
# we want to remove the old one (if it exists) and then initialize and add the new dialog
# box type to the dialog player
if type != _preserved_type_player_type:
if _dialog_player.get_children().has(_type_player):
_dialog_player.remove_child(_type_player)
_init_type_player(type)
_preserved_type_player_type = type
else:
_init_type_player(type)
state_machine._change_state("say")
func do_say(global_id: String, text: String) -> void: func do_say(global_id: String, text: String) -> void:
# Only add_child here in order to prevent _type_player from running its _process method # Only add_child here in order to prevent _type_player from running its _process method
@@ -116,8 +66,6 @@ func do_say(global_id: String, text: String) -> void:
_type_player.say(global_id, text) _type_player.say(global_id, text)
func _init_type_player(type: String) -> void: func _init_type_player(type: String) -> void:
if type == "floating": if type == "floating":
_type_player = preload(\ _type_player = preload(\
@@ -131,16 +79,8 @@ func _init_type_player(type: String) -> void:
"res://addons/escoria-ui-return-monkey-island-dialog-simple/dialog_tip.tscn"\ "res://addons/escoria-ui-return-monkey-island-dialog-simple/dialog_tip.tscn"\
).instantiate() ).instantiate()
_type_player.connect("say_finished", Callable(self, "_on_say_finished")) _type_player.say_finished.connect(_on_say_finished)
_type_player.connect("say_visible", Callable(self, "_on_say_visible")) _type_player.say_visible.connect(_on_say_visible)
func _initialize_say_states(global_id: String, text: String, type: String) -> void:
state_machine.states_map["say"].initialize(self, global_id, text, type)
state_machine.states_map["finish"].initialize(_dialog_player)
state_machine.states_map["say_fast"].initialize(self)
state_machine.states_map["say_finish"].initialize(self)
state_machine.states_map["visible"].initialize(self)
state_machine.states_map["interrupt"].initialize(self)
func _on_say_finished(): func _on_say_finished():
@@ -150,10 +90,11 @@ func _on_say_finished():
_is_saying = false _is_saying = false
emit_signal("say_finished") say_finished.emit()
func _on_say_visible(): func _on_say_visible():
emit_signal("say_visible") say_visible.emit()
# Present an option chooser to the player and sends the signal # Present an option chooser to the player and sends the signal
@@ -188,19 +129,7 @@ func do_choose(dialog_player: Node, dialog: ESCDialog, type: String = "simple"):
if option is ESCDialogOption: if option is ESCDialogOption:
escoria.globals_manager.set_global("ESC_DIALOG_CHOSEN_OPTION", option.option) escoria.globals_manager.set_global("ESC_DIALOG_CHOSEN_OPTION", option.option)
# END MODIFIED FOR RETURN TO MONKEY UI # END MODIFIED FOR RETURN TO MONKEY UI
emit_signal("option_chosen", option) option_chosen.emit(option)
# Trigger running the dialogue faster
func speedup():
if is_instance_valid(_type_player):
_type_player.speedup()
# Trigger an instant finish of the current dialog
func finish():
if is_instance_valid(_type_player):
_type_player.finish()
# The say command has been interrupted, cancel the dialog display # The say command has been interrupted, cancel the dialog display
@@ -215,21 +144,17 @@ func interrupt():
_dialog_player.remove_child(_type_player) _dialog_player.remove_child(_type_player)
remove_dialop_tip() remove_dialop_tip()
emit_signal("say_finished") say_finished.emit()
# To be called if voice audio has finished.
func voice_audio_finished():
if is_instance_valid(_type_player):
_type_player.voice_audio_finished()
func add_dialog_tip(): func add_dialog_tip():
if not _dialog_player.get_children().has(_dialog_tip): if not _dialog_player.get_children().has(_dialog_tip):
_dialog_player.add_child(_dialog_tip) _dialog_player.add_child(_dialog_tip)
func remove_dialop_tip(): func remove_dialop_tip():
_dialog_player.remove_child(_dialog_tip) _dialog_player.remove_child(_dialog_tip)
func _on_paused(): func _on_paused():
remove_dialop_tip() remove_dialop_tip()

View File

@@ -40,5 +40,5 @@ func update(_delta):
escoria.logger.trace(self, "Dialog State Machine: 'choices' -> 'idle'") escoria.logger.trace(self, "Dialog State Machine: 'choices' -> 'idle'")
emit_signal("finished", "idle") finished.emit("idle")
_dialog_player.emit_signal("option_chosen", option) _dialog_player.option_chosen.emit(option)

View File

@@ -17,5 +17,5 @@ func enter():
func update(_delta): func update(_delta):
escoria.logger.trace(self, "Dialog State Machine: 'finish' -> 'idle'") escoria.logger.trace(self, "Dialog State Machine: 'finish' -> 'idle'")
emit_signal("finished", "idle") finished.emit("idle")
_dialog_player.emit_signal("say_finished") _dialog_player.say_finished.emit()

View File

@@ -13,13 +13,12 @@ func enter():
escoria.logger.trace(self, "Dialog State Machine: Entered 'interrupt'.") escoria.logger.trace(self, "Dialog State Machine: Entered 'interrupt'.")
if _dialog_manager != null: if _dialog_manager != null:
if not _dialog_manager.is_connected("say_finished", Callable(self, "_on_say_finished")): if not _dialog_manager.say_finished.is_connected(_on_say_finished):
_dialog_manager.connect("say_finished", Callable(self, "_on_say_finished")) _dialog_manager.say_finished.connect(_on_say_finished)
_dialog_manager.interrupt() _dialog_manager.interrupt()
func _on_say_finished() -> void: func _on_say_finished() -> void:
escoria.logger.trace(self, "Dialog State Machine: 'interrupt' -> 'finish'") escoria.logger.trace(self, "Dialog State Machine: 'interrupt' -> 'finish'")
emit_signal("finished", "finish") finished.emit("finish")

View File

@@ -1,10 +1,6 @@
extends "res://addons/escoria-ui-return-monkey-island-dialog-simple/patterns/state_machine/state.gd" extends "res://addons/escoria-ui-return-monkey-island-dialog-simple/patterns/state_machine/state.gd"
# A regular expression that separates the translation key from the text
const KEYTEXT_REGEX = "^((?<key>[^:]+):)?\"(?<text>.+)\""
# Reference to the currently playing dialog manager # Reference to the currently playing dialog manager
var _dialog_manager: ESCDialogManager = null var _dialog_manager: ESCDialogManager = null
@@ -14,27 +10,28 @@ var _character: String
# UI to use for the dialog # UI to use for the dialog
var _type: String var _type: String
# Translation key
var _key: String = ""
# Text to say # Text to say
var _text: String var _text: String
# Regular expression object for the separation of key and text
var _keytext_regex: RegEx = RegEx.new()
var _ready_to_say: bool var _ready_to_say: bool
# flag for whether the dialog manager has started to "say" to ensure that it has
# prior to exiting this state as other states need this to happen to continue
# (other states rely on the setup that the dialog manager does)
var _say_started: bool
var _stop_talking_animation_on_option: String var _stop_talking_animation_on_option: String
# Constructor func initialize(dialog_manager: ESCDialogManager, character: String, text: String, type: String, key: String) -> void:
func _init() -> void:
_keytext_regex.compile(KEYTEXT_REGEX)
func initialize(dialog_manager: ESCDialogManager, character: String, text: String, type: String) -> void:
_dialog_manager = dialog_manager _dialog_manager = dialog_manager
_character = character _character = character
_text = text _text = text
_type = type _type = type
_key = key
_stop_talking_animation_on_option = \ _stop_talking_animation_on_option = \
ESCProjectSettingsManager.get_setting(RTMISimpleDialogSettings.STOP_TALKING_ANIMATION_ON) ESCProjectSettingsManager.get_setting(RTMISimpleDialogSettings.STOP_TALKING_ANIMATION_ON)
@@ -53,17 +50,17 @@ func handle_input(_event):
func _handle_left_click_action(left_click_action: String) -> void: func _handle_left_click_action(left_click_action: String) -> void:
match left_click_action: match left_click_action:
RTMISimpleDialogSettings.LEFT_CLICK_ACTION_SPEED_UP: RTMISimpleDialogSettings.LEFT_CLICK_ACTION_SPEED_UP:
if _dialog_manager.is_connected("say_visible", Callable(self, "_on_say_visible")): if _dialog_manager.say_visible.is_connected(_on_say_visible):
_dialog_manager.disconnect("say_visible", Callable(self, "_on_say_visible")) _dialog_manager.say_visible.disconnect(_on_say_visible)
escoria.logger.trace(self, "Dialog State Machine: 'say' -> 'say_fast'") escoria.logger.trace(self, "Dialog State Machine: 'say' -> 'say_fast'")
emit_signal("finished", "say_fast") finished.emit("say_fast")
RTMISimpleDialogSettings.LEFT_CLICK_ACTION_INSTANT_FINISH: RTMISimpleDialogSettings.LEFT_CLICK_ACTION_INSTANT_FINISH:
if _dialog_manager.is_connected("say_visible", Callable(self, "_on_say_visible")): if _dialog_manager.say_visible.is_connected(_on_say_visible):
_dialog_manager.disconnect("say_visible", Callable(self, "_on_say_visible")) _dialog_manager.say_visible.disconnect(_on_say_visible)
escoria.logger.trace(self, "Dialog State Machine: 'say' -> 'say_finish'") escoria.logger.trace(self, "Dialog State Machine: 'say' -> 'say_finish'")
emit_signal("finished", "say_finish") finished.emit("say_finish")
get_viewport().set_input_as_handled() get_viewport().set_input_as_handled()
@@ -71,28 +68,18 @@ func _handle_left_click_action(left_click_action: String) -> void:
func enter(): func enter():
escoria.logger.trace(self, "Dialog State Machine: Entered 'say'.") escoria.logger.trace(self, "Dialog State Machine: Entered 'say'.")
if not _dialog_manager.is_connected("say_visible", Callable(self, "_on_say_visible")): _say_started = false
_dialog_manager.connect("say_visible", Callable(self, "_on_say_visible"))
var matches = _keytext_regex.search(_text) if not _dialog_manager.say_visible.is_connected(_on_say_visible):
_dialog_manager.say_visible.connect(_on_say_visible)
if not matches: if _key and not _key.is_empty():
escoria.logger.error( var _speech_resource = _get_voice_file(_key)
self,
"Unexpected text encountered: %s." % _text
)
var key = matches.get_string("key")
if matches.get_string("key") != "":
var _speech_resource = _get_voice_file(
matches.get_string("key")
)
if _speech_resource == "": if _speech_resource == "":
escoria.logger.warn( escoria.logger.warn(
self, self,
"Unable to find voice file with key '%s'." % matches.get_string("key") "Unable to find voice file with key '%s'." % _key
) )
else: else:
( (
@@ -104,34 +91,38 @@ func enter():
if not ( if not (
escoria.object_manager.get_object(escoria.object_manager.SPEECH).node\ escoria.object_manager.get_object(escoria.object_manager.SPEECH).node\
as ESCSpeechPlayer as ESCSpeechPlayer
).stream.is_connected("finished", Callable(self, "_on_audio_finished")): ).stream.finished.is_connected(_on_audio_finished):
( (
escoria.object_manager.get_object(escoria.object_manager.SPEECH).node\ escoria.object_manager.get_object(escoria.object_manager.SPEECH).node\
as ESCSpeechPlayer as ESCSpeechPlayer
).stream.connect("finished", Callable(self, "_on_audio_finished")) ).stream.finished.connect(_on_audio_finished)
var translated_text: String = tr(matches.get_string("key")) var translated_text: String = tr(_key)
# Only update the text if the translated text was found; otherwise, raise # Only update the text if the translated text was found; otherwise, raise
# a warning and use the original, untranslated text. # a warning and use the original, untranslated text.
if translated_text == matches.get_string("key"): if translated_text == _key:
escoria.logger.warn( escoria.logger.warn(
self, self,
"Unable to find position key '%s'. Using untranslated text." % matches.get_string("key") "Unable to find translation key '%s'. Using untranslated text." % _key
) )
_text = matches.get_string("text")
else: else:
_text = translated_text _text = translated_text
else:
_text = matches.get_string("text")
_ready_to_say = true _ready_to_say = true
func exit() -> void:
if not _say_started:
_dialog_manager.do_say(_character, _text)
_say_started = true
func update(_delta): func update(_delta):
if _ready_to_say: if _ready_to_say:
_dialog_manager.do_say(_character, _text) _dialog_manager.do_say(_character, _text)
_say_started = true
_ready_to_say = false _ready_to_say = false
@@ -148,15 +139,15 @@ func _get_voice_file(key: String, start: String = "") -> String:
start = ESCProjectSettingsManager.get_setting( start = ESCProjectSettingsManager.get_setting(
ESCProjectSettingsManager.SPEECH_FOLDER ESCProjectSettingsManager.SPEECH_FOLDER
) )
var _dir = DirAccess.new() var _dir = DirAccess.open(start)
if _dir.open(start) == OK: if _dir != null:
_dir.list_dir_begin() # TODOConverter3To4 fill missing arguments https://github.com/godotengine/godot/pull/40547 _dir.list_dir_begin() # TODOConverter3To4 fill missing arguments https://github.com/godotengine/godot/pull/40547
var file_name = _dir.get_next() var file_name = _dir.get_next()
while file_name != "": while file_name != "":
if _dir.current_is_dir(): if _dir.current_is_dir():
var _voice_file = _get_voice_file( var _voice_file = _get_voice_file(
key, key,
start.plus_file(file_name) start.path_join(file_name)
) )
if _voice_file != "": if _voice_file != "":
return _voice_file return _voice_file
@@ -167,14 +158,14 @@ func _get_voice_file(key: String, start: String = "") -> String:
ESCProjectSettingsManager.SPEECH_EXTENSION ESCProjectSettingsManager.SPEECH_EXTENSION
) )
]: ]:
return start.plus_file(file_name.trim_suffix(".import")) return start.path_join(file_name.trim_suffix(".import"))
file_name = _dir.get_next() file_name = _dir.get_next()
return "" return ""
func _on_say_visible() -> void: func _on_say_visible() -> void:
escoria.logger.trace(self, "Dialog State Machine: 'say' -> 'visible'") escoria.logger.trace(self, "Dialog State Machine: 'say' -> 'visible'")
emit_signal("finished", "visible") finished.emit("visible")
func _on_audio_finished() -> void: func _on_audio_finished() -> void:

View File

@@ -16,8 +16,8 @@ func enter():
escoria.inputs_manager.INPUT_NONE and \ escoria.inputs_manager.INPUT_NONE and \
_dialog_manager != null: _dialog_manager != null:
if not _dialog_manager.is_connected("say_visible", Callable(self, "_on_say_visible")): if not _dialog_manager.say_visible.is_connected(_on_say_visible):
_dialog_manager.connect("say_visible", Callable(self, "_on_say_visible")) _dialog_manager.say_visible.connect(_on_say_visible)
_dialog_manager.speedup() _dialog_manager.speedup()
else: else:
@@ -26,4 +26,4 @@ func enter():
func _on_say_visible() -> void: func _on_say_visible() -> void:
escoria.logger.trace(self, "Dialog State Machine: 'say_fast' -> 'visible'") escoria.logger.trace(self, "Dialog State Machine: 'say_fast' -> 'visible'")
emit_signal("finished", "visible") finished.emit("visible")

View File

@@ -16,8 +16,8 @@ func enter():
escoria.inputs_manager.INPUT_NONE and \ escoria.inputs_manager.INPUT_NONE and \
_dialog_manager != null: _dialog_manager != null:
if not _dialog_manager.is_connected("say_visible", Callable(self, "_on_say_visible")): if not _dialog_manager.say_visible.is_connected(_on_say_visible):
_dialog_manager.connect("say_visible", Callable(self, "_on_say_visible")) _dialog_manager.say_visible.connect(_on_say_visible)
_dialog_manager.finish() _dialog_manager.finish()
else: else:
@@ -26,4 +26,4 @@ func enter():
func _on_say_visible() -> void: func _on_say_visible() -> void:
escoria.logger.trace(self, "Dialog State Machine: 'say_finish' -> 'visible'") escoria.logger.trace(self, "Dialog State Machine: 'say_finish' -> 'visible'")
emit_signal("finished", "visible") finished.emit("visible")

View File

@@ -12,8 +12,8 @@ func initialize(dialog_manager: ESCDialogManager) -> void:
func enter(): func enter():
escoria.logger.trace(self, "Dialog State Machine: Entered 'visible'.") escoria.logger.trace(self, "Dialog State Machine: Entered 'visible'.")
if not _dialog_manager.is_connected("say_finished", Callable(self, "_on_say_finished")): if not _dialog_manager.say_finished.is_connected(_on_say_finished):
_dialog_manager.connect("say_finished", Callable(self, "_on_say_finished")) _dialog_manager.say_finished.connect(_on_say_finished)
func handle_input(_event): func handle_input(_event):
@@ -21,14 +21,14 @@ func handle_input(_event):
if escoria.inputs_manager.input_mode != \ if escoria.inputs_manager.input_mode != \
escoria.inputs_manager.INPUT_NONE: escoria.inputs_manager.INPUT_NONE:
if _dialog_manager.is_connected("say_finished", Callable(self, "_on_say_finished")): if _dialog_manager.say_finished.is_connected(_on_say_finished):
_dialog_manager.disconnect("say_finished", Callable(self, "_on_say_finished")) _dialog_manager.say_finished.disconnect(_on_say_finished)
emit_signal("finished", "interrupt") finished.emit("interrupt")
get_viewport().set_input_as_handled() get_viewport().set_input_as_handled()
# Handles the end of a say function after it has emitted say_finished. # Handles the end of a say function after it has emitted say_finished.
func _on_say_finished(): func _on_say_finished():
escoria.logger.trace(self, "Dialog State Machine: 'visible' -> 'finish'") escoria.logger.trace(self, "Dialog State Machine: 'visible' -> 'finish'")
emit_signal("finished", "finish") finished.emit("finish")

View File

@@ -37,13 +37,12 @@ var _current_line: String
@onready var text_node = $Panel/MarginContainer/HSplitContainer/text @onready var text_node = $Panel/MarginContainer/HSplitContainer/text
# The tween node for text animations # The tween node for text animations
@onready var tween = $Panel/MarginContainer/HSplitContainer/text/Tween @onready var tween: Tween3 = Tween3.new(self)
# Whether the dialog manager is paused # Whether the dialog manager is paused
@onready var is_paused: bool = true @onready var is_paused: bool = true
# Build up the UI # Build up the UI
func _ready(): func _ready():
_text_time_per_character = ProjectSettings.get_setting( _text_time_per_character = ProjectSettings.get_setting(
@@ -56,11 +55,11 @@ func _ready():
"%s setting must be a non-negative number. Will use default value of %s." % "%s setting must be a non-negative number. Will use default value of %s." %
[ [
RTMISimpleDialogSettings.TEXT_TIME_PER_LETTER_MS, RTMISimpleDialogSettings.TEXT_TIME_PER_LETTER_MS,
RTMISimpleDialogSettings.TEXT_TIME_PER_LETTER_MS_DEFAULT_VALUE escoria.TEXT_TIME_PER_LETTER_MS_DEFAULT_VALUE
] ]
) )
_text_time_per_character = RTMISimpleDialogSettings.TEXT_TIME_PER_LETTER_MS_DEFAULT_VALUE _text_time_per_character = escoria.TEXT_TIME_PER_LETTER_MS_DEFAULT_VALUE
_fast_text_time_per_character = ProjectSettings.get_setting( _fast_text_time_per_character = ProjectSettings.get_setting(
RTMISimpleDialogSettings.TEXT_TIME_PER_LETTER_MS_FAST RTMISimpleDialogSettings.TEXT_TIME_PER_LETTER_MS_FAST
@@ -72,11 +71,11 @@ func _ready():
"%s setting must be a non-negative number. Will use default value of %s." % "%s setting must be a non-negative number. Will use default value of %s." %
[ [
RTMISimpleDialogSettings.TEXT_TIME_PER_LETTER_MS_FAST, RTMISimpleDialogSettings.TEXT_TIME_PER_LETTER_MS_FAST,
RTMISimpleDialogSettings.TEXT_TIME_PER_LETTER_MS_FAST_DEFAULT_VALUE escoria.TEXT_TIME_PER_LETTER_MS_FAST_DEFAULT_VALUE
] ]
) )
_fast_text_time_per_character = RTMISimpleDialogSettings.TEXT_TIME_PER_LETTER_MS_FAST_DEFAULT_VALUE _fast_text_time_per_character = escoria.TEXT_TIME_PER_LETTER_MS_FAST_DEFAULT_VALUE
_reading_speed_in_wpm = ProjectSettings.get_setting( _reading_speed_in_wpm = ProjectSettings.get_setting(
RTMISimpleDialogSettings.READING_SPEED_IN_WPM RTMISimpleDialogSettings.READING_SPEED_IN_WPM
@@ -88,23 +87,21 @@ func _ready():
"%s setting must be a positive number. Will use default value of %s." % "%s setting must be a positive number. Will use default value of %s." %
[ [
RTMISimpleDialogSettings.READING_SPEED_IN_WPM, RTMISimpleDialogSettings.READING_SPEED_IN_WPM,
RTMISimpleDialogSettings.READING_SPEED_IN_WPM_DEFAULT_VALUE escoria.READING_SPEED_IN_WPM_DEFAULT_VALUE
] ]
) )
_reading_speed_in_wpm = RTMISimpleDialogSettings.READING_SPEED_IN_WPM_DEFAULT_VALUE _reading_speed_in_wpm = escoria.READING_SPEED_IN_WPM_DEFAULT_VALUE
_word_regex.compile("\\S+") _word_regex.compile("\\S+")
text_node.bbcode_enabled = true text_node.bbcode_enabled = true
tween.connect( tween.finished.connect(_on_dialog_line_typed.bind("", ""))
"tween_completed",
self,
"_on_dialog_line_typed"
)
escoria.connect("paused", Callable(self, "_on_paused")) escoria.paused.connect(_on_paused)
escoria.connect("resumed", Callable(self, "_on_resumed")) escoria.resumed.connect(_on_resumed)
tree_exited.connect(_on_tree_exited)
# Switch the current character # Switch the current character
@@ -145,13 +142,15 @@ func say(character: String, line: String):
text_node.text = tr(line) text_node.text = tr(line)
text_node.percent_visible = 0.0 text_node.visible_ratio = 0.0
var time_show_full_text = _text_time_per_character / 1000 * len(line) var time_show_full_text = _text_time_per_character / 1000 * len(line)
tween.interpolate_property(text_node, "percent_visible", tween.reset()
tween.interpolate_property(text_node, "visible_ratio",
0.0, 1.0, time_show_full_text, 0.0, 1.0, time_show_full_text,
Tween.TRANS_LINEAR, Tween.EASE_IN_OUT) Tween.TRANS_LINEAR, Tween.EASE_IN_OUT)
tween.start() tween.play()
# Called by the dialog player when the # Called by the dialog player when the
@@ -159,19 +158,19 @@ func speedup():
if not _is_speeding_up: if not _is_speeding_up:
_is_speeding_up = true _is_speeding_up = true
var time_show_full_text = _fast_text_time_per_character / 1000 * len(_current_line) var time_show_full_text = _fast_text_time_per_character / 1000 * len(_current_line)
tween.remove_all() tween.reset()
tween.interpolate_property(text_node, "percent_visible", tween.interpolate_property(text_node, "visible_ratio",
text_node.percent_visible, 1.0, time_show_full_text, text_node.visible_ratio, 1.0, time_show_full_text,
Tween.TRANS_LINEAR, Tween.EASE_IN_OUT) Tween.TRANS_LINEAR, Tween.EASE_IN_OUT)
tween.start() tween.play()
# Called by the dialog player when user wants to finish dialogue immediately. # Called by the dialog player when user wants to finish dialogue immediately.
func finish(): func finish():
tween.remove_all() tween.reset()
tween.interpolate_property(text_node, "percent_visible", tween.interpolate_property(text_node, "visible_ratio",
text_node.percent_visible, 1.0, 0.0) text_node.visible_ratio, 1.0, 0.0)
tween.start() tween.play()
# To be called if voice audio has finished. # To be called if voice audio has finished.
@@ -191,10 +190,13 @@ func _on_dialog_line_typed(object, key):
text_node.visible_characters = -1 text_node.visible_characters = -1
var time_to_disappear: float = _calculate_time_to_disappear() var time_to_disappear: float = _calculate_time_to_disappear()
$Timer.start(time_to_disappear)
$Timer.connect("timeout", Callable(self, "_on_dialog_finished"))
emit_signal("say_visible") if not $Timer.timeout.is_connected(_on_dialog_finished):
$Timer.timeout.connect(_on_dialog_finished)
$Timer.start(time_to_disappear)
say_visible.emit()
func _calculate_time_to_disappear() -> float: func _calculate_time_to_disappear() -> float:
@@ -207,21 +209,31 @@ func _get_number_of_words() -> int:
# Ending the dialog # Ending the dialog
func _on_dialog_finished(): func _on_dialog_finished():
$Timer.stop()
# Only trigger to clear the text if we aren't limiting the clearing trigger to a click. # Only trigger to clear the text if we aren't limiting the clearing trigger to a click.
if not ESCProjectSettingsManager.get_setting(RTMISimpleDialogSettings.CLEAR_TEXT_BY_CLICK_ONLY): if not ESCProjectSettingsManager.get_setting(RTMISimpleDialogSettings.CLEAR_TEXT_BY_CLICK_ONLY):
emit_signal("say_finished") say_finished.emit()
queue_free()
# Handler managing pause notification from Escoria # Handler managing pause notification from Escoria
func _on_paused(): func _on_paused():
if tween.is_active(): if tween.is_running():
is_paused = true is_paused = true
tween.stop_all() tween.stop()
# Handler managing resume notification from Escoria # Handler managing resume notification from Escoria
func _on_resumed(): func _on_resumed():
if not tween.is_active(): if not tween.is_running():
# We can't rely on "show()" to make an invisible popup reappear, as per the docs for
# CanvasItem. Instead, we need to use one of the popup_* methods.
if is_inside_tree():
popup_centered()
is_paused = false is_paused = false
tween.resume_all() tween.resume()
func _on_tree_exited():
queue_free()

View File

@@ -1,68 +1,46 @@
[gd_scene load_steps=2 format=2] [gd_scene load_steps=2 format=3 uid="uid://cp75ofyuetxux"]
[ext_resource path="res://addons/escoria-ui-return-monkey-island-dialog-simple/types/avatar.gd" type="Script" id=1] [ext_resource type="Script" path="res://addons/escoria-ui-return-monkey-island-dialog-simple/types/avatar.gd" id="1"]
[node name="dialog_box" type="Popup"] [node name="dialog_box" type="Popup"]
anchor_right = 1.0 size = Vector2i(510, 180)
anchor_bottom = 1.0 visible = true
offset_right = -782.0 script = ExtResource("1")
offset_bottom = -734.0
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
[node name="Timer" type="Timer" parent="."] [node name="Timer" type="Timer" parent="."]
[node name="Panel" type="Panel" parent="."] [node name="Panel" type="Panel" parent="."]
anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0
__meta__ = {
"_edit_use_anchors_": false
}
[node name="MarginContainer" type="MarginContainer" parent="Panel"] [node name="MarginContainer" type="MarginContainer" parent="Panel"]
layout_mode = 0
anchor_right = 1.0 anchor_right = 1.0
anchor_bottom = 1.0 anchor_bottom = 1.0
theme_override_constants/margin_right = 20
theme_override_constants/margin_top = 20
theme_override_constants/margin_left = 20 theme_override_constants/margin_left = 20
theme_override_constants/margin_top = 20
theme_override_constants/margin_right = 20
theme_override_constants/margin_bottom = 20 theme_override_constants/margin_bottom = 20
__meta__ = {
"_edit_use_anchors_": false
}
[node name="HSplitContainer" type="HSplitContainer" parent="Panel/MarginContainer"] [node name="HSplitContainer" type="HSplitContainer" parent="Panel/MarginContainer"]
offset_left = 20.0 layout_mode = 2
offset_top = 20.0
offset_right = 478.0
offset_bottom = 146.0
theme_override_constants/separation = 35 theme_override_constants/separation = 35
dragger_visibility = 1 dragger_visibility = 1
[node name="VBoxContainer" type="VBoxContainer" parent="Panel/MarginContainer/HSplitContainer"] [node name="VBoxContainer" type="VBoxContainer" parent="Panel/MarginContainer/HSplitContainer"]
offset_right = 88.0 layout_mode = 2
offset_bottom = 126.0
size_flags_horizontal = 3 size_flags_horizontal = 3
size_flags_stretch_ratio = 0.3 size_flags_stretch_ratio = 0.3
[node name="avatar" type="TextureRect" parent="Panel/MarginContainer/HSplitContainer/VBoxContainer"] [node name="avatar" type="TextureRect" parent="Panel/MarginContainer/HSplitContainer/VBoxContainer"]
offset_right = 88.0 layout_mode = 2
offset_bottom = 108.0
size_flags_horizontal = 3 size_flags_horizontal = 3
size_flags_vertical = 3 size_flags_vertical = 3
expand = true stretch_mode = 4
[node name="text" type="RichTextLabel" parent="Panel/MarginContainer/HSplitContainer"] [node name="text" type="RichTextLabel" parent="Panel/MarginContainer/HSplitContainer"]
offset_left = 123.0 layout_mode = 2
offset_right = 458.0
offset_bottom = 126.0
size_flags_horizontal = 3 size_flags_horizontal = 3
bbcode_enabled = true bbcode_enabled = true
text = "Here be some text" 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"]

View File

@@ -34,7 +34,7 @@ var _current_line: String
# Tween node for text animation # Tween node for text animation
@onready var tween: Tween = $Tween @onready var tween: Tween3 = Tween3.new(self)
# The node showing the text # The node showing the text
@onready var text_node: RichTextLabel = self @onready var text_node: RichTextLabel = self
@@ -42,6 +42,7 @@ var _current_line: String
# Whether the dialog manager is paused # Whether the dialog manager is paused
@onready var is_paused: bool = true @onready var is_paused: bool = true
var dialog_location_node = null
# Enable bbcode and catch the signal when a tween completed # Enable bbcode and catch the signal when a tween completed
func _ready(): func _ready():
@@ -55,11 +56,11 @@ func _ready():
"%s setting must be a non-negative number. Will use default value of %s." % "%s setting must be a non-negative number. Will use default value of %s." %
[ [
RTMISimpleDialogSettings.TEXT_TIME_PER_LETTER_MS, RTMISimpleDialogSettings.TEXT_TIME_PER_LETTER_MS,
RTMISimpleDialogSettings.TEXT_TIME_PER_LETTER_MS_DEFAULT_VALUE escoria.TEXT_TIME_PER_LETTER_MS_DEFAULT_VALUE
] ]
) )
_text_time_per_character = RTMISimpleDialogSettings.TEXT_TIME_PER_LETTER_MS_DEFAULT_VALUE _text_time_per_character = escoria.TEXT_TIME_PER_LETTER_MS_DEFAULT_VALUE
_fast_text_time_per_character = ProjectSettings.get_setting( _fast_text_time_per_character = ProjectSettings.get_setting(
RTMISimpleDialogSettings.TEXT_TIME_PER_LETTER_MS_FAST RTMISimpleDialogSettings.TEXT_TIME_PER_LETTER_MS_FAST
@@ -71,11 +72,11 @@ func _ready():
"%s setting must be a non-negative number. Will use default value of %s." % "%s setting must be a non-negative number. Will use default value of %s." %
[ [
RTMISimpleDialogSettings.TEXT_TIME_PER_LETTER_MS_FAST, RTMISimpleDialogSettings.TEXT_TIME_PER_LETTER_MS_FAST,
RTMISimpleDialogSettings.TEXT_TIME_PER_LETTER_MS_FAST_DEFAULT_VALUE escoria.TEXT_TIME_PER_LETTER_MS_FAST_DEFAULT_VALUE
] ]
) )
_fast_text_time_per_character = RTMISimpleDialogSettings.TEXT_TIME_PER_LETTER_MS_FAST_DEFAULT_VALUE _fast_text_time_per_character = escoria.TEXT_TIME_PER_LETTER_MS_FAST_DEFAULT_VALUE
_reading_speed_in_wpm = ProjectSettings.get_setting( _reading_speed_in_wpm = ProjectSettings.get_setting(
RTMISimpleDialogSettings.READING_SPEED_IN_WPM RTMISimpleDialogSettings.READING_SPEED_IN_WPM
@@ -87,41 +88,36 @@ func _ready():
"%s setting must be a positive number. Will use default value of %s." % "%s setting must be a positive number. Will use default value of %s." %
[ [
RTMISimpleDialogSettings.READING_SPEED_IN_WPM, RTMISimpleDialogSettings.READING_SPEED_IN_WPM,
RTMISimpleDialogSettings.READING_SPEED_IN_WPM_DEFAULT_VALUE escoria.READING_SPEED_IN_WPM_DEFAULT_VALUE
] ]
) )
_reading_speed_in_wpm = RTMISimpleDialogSettings.READING_SPEED_IN_WPM_DEFAULT_VALUE _reading_speed_in_wpm = escoria.READING_SPEED_IN_WPM_DEFAULT_VALUE
_word_regex.compile("\\S+") _word_regex.compile("\\S+")
bbcode_enabled = true bbcode_enabled = true
$Tween.connect("tween_completed", Callable(self, "_on_dialog_line_typed"))
connect("tree_exiting", Callable(self, "_on_tree_exiting")) tween.finished.connect(_on_dialog_line_typed.bind("", ""))
escoria.connect("paused", Callable(self, "_on_paused")) tree_exiting.connect(_on_tree_exiting)
escoria.connect("resumed", Callable(self, "_on_resumed"))
escoria.paused.connect(_on_paused)
escoria.resumed.connect(_on_resumed)
_current_line = "" _current_line = ""
func _process(delta): func _process(delta):
if _current_character.is_inside_tree() and \ if _current_character.is_inside_tree() and \
_current_character.has_node("dialog_position"): is_instance_valid(dialog_location_node):
# Position the RichTextLabel on the character's dialog position, if any. # Position the RichTextLabel on the character's dialog position, if any.
position = _current_character.get_node("dialog_position") \ position = dialog_location_node.get_global_transform_with_canvas().origin
super.get_global_transform_with_canvas().origin
position.x -= size.x / 2 position.x -= size.x / 2
if position.x < 0: _account_for_margin_x()
position.x = 0
var screen_margin = position.x + size.x - \ _account_for_margin_y()
ProjectSettings.get("display/window/size/viewport_width")
if screen_margin > 0:
position.x -= screen_margin
# Make a character say something # Make a character say something
@@ -139,41 +135,52 @@ func say(character: String, line: String) :
# Position the RichTextLabel on the character's dialog position, if any. # Position the RichTextLabel on the character's dialog position, if any.
_current_character = escoria.object_manager.get_object(character).node _current_character = escoria.object_manager.get_object(character).node
var dialog_location_count:int = 0
for c in escoria.object_manager.get_object(character).node.get_children():
if c is Marker2D:
# Identify any Postion2D nodes
if c is ESCDialogLocation:
dialog_location_count += 1
dialog_location_node = c
if dialog_location_count > 1:
escoria.logger.warn(
self,
"Multiple ESCDialogLocation nodes found " +
"object %s. Last one will be used." % _current_character)
# Set text color to color set in the actor # Set text color to color set in the actor
var text_color = _current_character.dialog_color var text_color = _current_character.dialog_color
var text_color_html = text_color.to_html(false) var text_color_html = text_color.to_html(false)
text_node.text = "[center][color=#" + text_color_html + "]" \ text_node.text = "[center][color=#" + text_color_html + "]" \
super.format([text_color_html]) + tr(line) + "[/color][center]" .format([text_color_html]) + tr(line) + "[/color][center]"
if _current_character.is_inside_tree() and \ if _current_character.is_inside_tree() and \
_current_character.has_node("dialog_position"): is_instance_valid(dialog_location_node):
position = _current_character.get_node( position = dialog_location_node.get_global_transform_with_canvas().origin
"dialog_position"
).get_global_transform_with_canvas().origin
position.x -= size.x / 2 position.x -= size.x / 2
else: else:
position.x = 0 position.x = 0
size.x = ProjectSettings.get_setting("display/window/size/viewport_width") size.x = ProjectSettings.get_setting("display/window/size/viewport_width")
if position.x < 0: _account_for_margin_x()
position.x = 0
var screen_margin = position.x + size.x - \ _account_for_margin_y()
ProjectSettings.get("display/window/size/viewport_width")
if screen_margin > 0:
position.x -= screen_margin
_current_character.start_talking() _current_character.start_talking()
text_node.percent_visible = 0.0 text_node.visible_ratio = 0.0
var time_show_full_text = _text_time_per_character / 1000 * len(_current_line) var time_show_full_text = _text_time_per_character / 1000 * len(_current_line)
tween.interpolate_property(text_node, "percent_visible", tween.reset()
tween.interpolate_property(text_node, "visible_ratio",
0.0, 1.0, time_show_full_text, 0.0, 1.0, time_show_full_text,
Tween.TRANS_LINEAR, Tween.EASE_IN_OUT) Tween.TRANS_LINEAR, Tween.EASE_IN_OUT)
tween.start() tween.play()
set_process(true) set_process(true)
@@ -183,19 +190,21 @@ func speedup():
_is_speeding_up = true _is_speeding_up = true
var time_show_full_text = _fast_text_time_per_character / 1000 * len(_current_line) var time_show_full_text = _fast_text_time_per_character / 1000 * len(_current_line)
tween.remove_all() tween.reset()
tween.interpolate_property(text_node, "percent_visible",
text_node.percent_visible, 1.0, time_show_full_text, tween.interpolate_property(text_node, "visible_ratio",
text_node.visible_ratio, 1.0, time_show_full_text,
Tween.TRANS_LINEAR, Tween.EASE_IN_OUT) Tween.TRANS_LINEAR, Tween.EASE_IN_OUT)
tween.start() tween.play()
# Called by the dialog player when user wants to finish dialogue immediately. # Called by the dialog player when user wants to finish dialogue immediately.
func finish(): func finish():
tween.remove_all() tween.reset()
tween.interpolate_property(text_node, "percent_visible",
text_node.percent_visible, 1.0, 0.0) tween.interpolate_property(text_node, "visible_ratio",
tween.start() text_node.visible_ratio, 1.0, 0.0)
tween.play()
# To be called if voice audio has finished. # To be called if voice audio has finished.
@@ -211,9 +220,9 @@ func _on_dialog_line_typed(object, key):
var time_to_disappear: float = _calculate_time_to_disappear() var time_to_disappear: float = _calculate_time_to_disappear()
$Timer.start(time_to_disappear) $Timer.start(time_to_disappear)
$Timer.connect("timeout", Callable(self, "_on_dialog_finished")) $Timer.timeout.connect(_on_dialog_finished)
emit_signal("say_visible") say_visible.emit()
func _calculate_time_to_disappear() -> float: func _calculate_time_to_disappear() -> float:
@@ -228,23 +237,23 @@ func _get_number_of_words() -> int:
func _on_dialog_finished(): func _on_dialog_finished():
# Only trigger to clear the text if we aren't limiting the clearing trigger to a click. # Only trigger to clear the text if we aren't limiting the clearing trigger to a click.
if not ESCProjectSettingsManager.get_setting(RTMISimpleDialogSettings.CLEAR_TEXT_BY_CLICK_ONLY): if not ESCProjectSettingsManager.get_setting(RTMISimpleDialogSettings.CLEAR_TEXT_BY_CLICK_ONLY):
emit_signal("say_finished") say_finished.emit()
# Handler managing pause notification from Escoria # Handler managing pause notification from Escoria
func _on_paused(): func _on_paused():
if tween.is_active(): if tween.is_running():
is_paused = true is_paused = true
visible = false visible = false
tween.stop_all() tween.stop()
# Handler managing resume notification from Escoria # Handler managing resume notification from Escoria
func _on_resumed(): func _on_resumed():
if not tween.is_active(): if not tween.is_running():
is_paused = false is_paused = false
visible = true visible = true
tween.resume_all() tween.resume()
# Handler to deal with this node being removed # Handler to deal with this node being removed
@@ -256,3 +265,25 @@ func _stop_character_talking():
# Make the speaking item animation stop talking, if it is still alive # Make the speaking item animation stop talking, if it is still alive
if is_instance_valid(_current_character) and _current_character != null: if is_instance_valid(_current_character) and _current_character != null:
_current_character.stop_talking() _current_character.stop_talking()
func _account_for_margin_x() -> void:
if position.x < 0:
position.x = 0
var screen_margin_x = position.x + size.x - \
ProjectSettings.get("display/window/size/viewport_width")
if screen_margin_x > 0:
position.x -= screen_margin_x
func _account_for_margin_y() -> void:
if position.y < 0:
position.y = 0
var screen_margin_y = position.y + size.y - \
ProjectSettings.get("display/window/size/viewport_height")
if screen_margin_y > 0:
position.y -= screen_margin_y

View File

@@ -1,14 +1,12 @@
extends VBoxContainer extends VBoxContainer
signal finished
func _ready(): func _ready():
pass # Replace with function body. pass # Replace with function body.
func _process(_delta): func _process(_delta):
var size = self.get_global_rect().size var textSize = self.get_global_rect().size
if escoria.globals_manager.get_global("turno_credits_rolling"): if escoria.globals_manager.get_global("turno_credits_rolling"):
self.set_position(self.get_position() + Vector2(0, -0.5)) self.set_position(self.get_position() + Vector2(0, -0.5))
if self.get_position().y < -20 - size.y: if self.get_position().y < -20 - textSize.y:
escoria.globals_manager.set_global("turno_credits_rolling", false) escoria.globals_manager.set_global("turno_credits_rolling", false)
emit_signal("finished") emit_signal("finished")