Skip to content

Commit

Permalink
feat: adds 'block_say' command/structure to allow for the reuse of di…
Browse files Browse the repository at this point in the history
…alog players, which can produce a better visual experience for characters that have speech spanning more than one dialog box
  • Loading branch information
BHSDuncan authored and StraToN committed May 14, 2023
1 parent af9efc3 commit 04121b0
Show file tree
Hide file tree
Showing 13 changed files with 252 additions and 25 deletions.
54 changes: 54 additions & 0 deletions addons/escoria-core/game/core-scripts/esc/commands/block_say.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# `block_say`
#
# `say` commands used subsequent to using the `block_say` command will reuse the
# dialog box type used by the next `say` command encountered. This reuse will
# continue until a call to `end_block_say` is made.
#
# Using `block_say` more than once prior to calling `end_block_say` has the following
# behaviour:
#
# - If no `say` command has yet been encountered since the first use of `block_say`,
# the result of using this command will be as described above.
# - If a `say` command has been encountered since the first use of `block_say`,
# the dialog box used with that `say` command will continue to be used for subsequent
# `say` commands. Note that the dialog box used with the next `say` command may be
# different than the one currently being reused.
#
# Example:
# `block say`
# `say player "Picture's looking good."`
# `say player "And so am I."`
# `end_block_say`
#
# @ESC
extends ESCBaseCommand
class_name BlockSayCommand


# Constructor
func _init() -> void:
pass


# Return the descriptor of the arguments of this command
func configure() -> ESCCommandArgumentDescriptor:
return ESCCommandArgumentDescriptor.new(0)


# Validate whether the given arguments match the command descriptor
func validate(arguments: Array):
return true


# Run the command
func run(command_params: Array) -> int:
escoria.dialog_player.enable_preserve_dialog_box()
return ESCExecution.RC_OK


# Function called when the command is interrupted.
func interrupt():
escoria.logger.debug(
self,
"[%s] interrupt() function not implemented." % get_command_name()
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# `end_block_say`
#
# `say` commands used subsequent to using the `end_block_say` command will no longer
# reuse the dialog box type used by the previous `say` command(s) encountered.
#
# Using `end_block_say` more than once is safe and idempotent.
#
# Example:
# `block say`
# `say player "Picture's looking good."`
# `say player "And so am I."`
# `end_block_say`
#
# @ESC
extends ESCBaseCommand
class_name EndBlockSayCommand


# Constructor
func _init() -> void:
pass


# Return the descriptor of the arguments of this command
func configure() -> ESCCommandArgumentDescriptor:
return ESCCommandArgumentDescriptor.new(0)


# Validate whether the given arguments match the command descriptor
func validate(arguments: Array):
return true


# Run the command
func run(command_params: Array) -> int:
escoria.dialog_player.disable_preserve_dialog_box()
return ESCExecution.RC_OK


# Function called when the command is interrupted.
func interrupt():
escoria.logger.debug(
self,
"[%s] interrupt() function not implemented." % get_command_name()
)
21 changes: 21 additions & 0 deletions addons/escoria-core/game/scenes/dialogs/esc_dialog_manager.gd
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,27 @@ func say(dialog_player: Node, global_id: String, text: String, type: String):
pass


# 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:
pass


# 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
# (this is the default state).
#
# This method should be idempotent, i.e. if called after the first time and
# prior to `enable_preserve_dialog_box` being called, the result should be the
# same.
func disable_preserve_dialog_box() -> void:
pass


# Present an option chooser to the player and sends the signal
# `option_chosen` with the chosen dialog option
#
Expand Down
28 changes: 28 additions & 0 deletions addons/escoria-core/game/scenes/dialogs/esc_dialog_player.gd
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ var _say_dialog_manager: ESCDialogManager = null
# Reference to the currently playing "choose" dialog manager
var _choose_dialog_manager: ESCDialogManager = null

# Whether to use the "dialog box preservation" feature
var _block_say_enabled: bool = false


# Register the dialog player and load the dialog resources
func _ready():
Expand All @@ -35,6 +38,28 @@ func _ready():
escoria.dialog_player = self


# 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:
_block_say_enabled = true


# 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
# (this is the default state).
#
# This method should be idempotent, i.e. if called after the first time and
# prior to `enable_preserve_dialog_box` being called, the result should be the
# same.
func disable_preserve_dialog_box() -> void:
_block_say_enabled = false
_say_dialog_manager.disable_preserve_dialog_box()


# Make a character say some text
#
# #### Parameters
Expand All @@ -52,6 +77,9 @@ func say(character: String, type: String, text: String) -> void:
# has changed since the last use of this method.
_update_dialog_manager(DIALOG_TYPE_SAY, _say_dialog_manager, type)

if _block_say_enabled:
_say_dialog_manager.enable_preserve_dialog_box()

_say_dialog_manager.say(self, character, text, type)


Expand Down
84 changes: 68 additions & 16 deletions addons/escoria-dialog-simple/esc_dialog_simple.gd
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@ var state_machine = preload("res://addons/escoria-dialog-simple/esc_dialog_simpl

# The currently running player
var _type_player: Node = null
var _type_player_type: String = ""

# Reference to the dialog player
var _dialog_player: Node = null

# Basic state tracking
var _is_saying: bool = false

# Whether to preserve the next dialog box used by `say`, or, if already
# preserving a dialog box, whether to continue using that dialog box
var _should_preserve_dialog_box: bool = false


func _ready() -> void:
add_child(state_machine)
Expand All @@ -39,6 +44,31 @@ 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
# dialog box or to not preserve the next dialog box used by a `say` command
# (this is the default state).
#
# This method should be idempotent, i.e. if called after the first time and
# prior to `enable_preserve_dialog_box` being called, the result should be the
# same.
func disable_preserve_dialog_box() -> void:
_should_preserve_dialog_box = false

if is_instance_valid(_dialog_player) and _dialog_player.get_children().has(_type_player):
_dialog_player.remove_child(_type_player)
_type_player_type = ""


# Output a text said by the item specified by the global id. Emit
# `say_finished` after finishing displaying the text.
#
Expand All @@ -53,17 +83,18 @@ func say(dialog_player: Node, global_id: String, text: String, type: String):

_initialize_say_states(global_id, text, type)

if type == "floating":
_type_player = preload(\
"res://addons/escoria-dialog-simple/types/floating.tscn"\
).instance()
else:
_type_player = preload(\
"res://addons/escoria-dialog-simple/types/avatar.tscn"\
).instance()
# Remove the current dialog box type if we have a new type that differs
# from the previously-used type AND we want to preserve the type
if _should_preserve_dialog_box:
if type != _type_player_type:
if _dialog_player.get_children().has(_type_player):
_dialog_player.remove_child(_type_player)

_type_player.connect("say_finished", self, "_on_say_finished", [], CONNECT_ONESHOT)
_type_player.connect("say_visible", self, "_on_say_visible", [], CONNECT_ONESHOT)
_init_type_player(type)
elif not _dialog_player.get_children().has(_type_player):
_init_type_player(type)
else:
_init_type_player(type)

state_machine._change_state("say")

Expand All @@ -75,11 +106,28 @@ func say(dialog_player: Node, global_id: String, text: String, type: String):

func do_say(global_id: String, text: String) -> void:
# Only add_child here in order to prevent _type_player from running its _process method
# before we're ready.
_dialog_player.add_child(_type_player)
# before we're ready, and only if it's necessary
if not _dialog_player.get_children().has(_type_player):
_dialog_player.add_child(_type_player)

_type_player.say(global_id, text)


func _init_type_player(type: String) -> void:
if type == "floating":
_type_player = preload(\
"res://addons/escoria-dialog-simple/types/floating.tscn"\
).instance()
else:
_type_player = preload(\
"res://addons/escoria-dialog-simple/types/avatar.tscn"\
).instance()

_type_player_type = type
_type_player.connect("say_finished", self, "_on_say_finished")
_type_player.connect("say_visible", self, "_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)
Expand All @@ -90,10 +138,12 @@ func _initialize_say_states(global_id: String, text: String, type: String) -> vo


func _on_say_finished():
if _dialog_player.get_children().has(_type_player):
if not _should_preserve_dialog_box and _dialog_player.get_children().has(_type_player):
_dialog_player.remove_child(_type_player)
_is_saying = false
emit_signal("say_finished")

_is_saying = false

emit_signal("say_finished")


func _on_say_visible():
Expand Down Expand Up @@ -151,7 +201,9 @@ func interrupt():
as ESCSpeechPlayer
).set_state("off")

_dialog_player.remove_child(_type_player)
if not _should_preserve_dialog_box and _dialog_player.get_children().has(_type_player):
_dialog_player.remove_child(_type_player)

emit_signal("say_finished")


Expand Down
2 changes: 1 addition & 1 deletion addons/escoria-dialog-simple/states/dialog_interrupt.gd
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func enter():

if _dialog_manager != null:
if not _dialog_manager.is_connected("say_finished", self, "_on_say_finished"):
_dialog_manager.connect("say_finished", self, "_on_say_finished", [], CONNECT_ONESHOT)
_dialog_manager.connect("say_finished", self, "_on_say_finished")

_dialog_manager.interrupt()

Expand Down
11 changes: 8 additions & 3 deletions addons/escoria-dialog-simple/states/dialog_say.gd
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func enter():
escoria.logger.trace(self, "Dialog State Machine: Entered 'say'.")

if not _dialog_manager.is_connected("say_visible", self, "_on_say_visible"):
_dialog_manager.connect("say_visible", self, "_on_say_visible", [], CONNECT_ONESHOT)
_dialog_manager.connect("say_visible", self, "_on_say_visible")

var matches = _keytext_regex.search(_text)

Expand Down Expand Up @@ -101,10 +101,15 @@ func enter():
).set_state(_speech_resource)

if _stop_talking_animation_on_option == SimpleDialogSettings.STOP_TALKING_ANIMATION_ON_END_OF_AUDIO:
(
if not (
escoria.object_manager.get_object(escoria.object_manager.SPEECH).node\
as ESCSpeechPlayer
).stream.connect("finished", self, "_on_audio_finished", [], CONNECT_ONESHOT)
).stream.is_connected("finished", self, "_on_audio_finished"):

(
escoria.object_manager.get_object(escoria.object_manager.SPEECH).node\
as ESCSpeechPlayer
).stream.connect("finished", self, "_on_audio_finished")

var translated_text: String = tr(matches.get_string("key"))

Expand Down
2 changes: 1 addition & 1 deletion addons/escoria-dialog-simple/states/dialog_say_fast.gd
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func enter():
_dialog_manager != null:

if not _dialog_manager.is_connected("say_visible", self, "_on_say_visible"):
_dialog_manager.connect("say_visible", self, "_on_say_visible", [], CONNECT_ONESHOT)
_dialog_manager.connect("say_visible", self, "_on_say_visible")

_dialog_manager.speedup()
else:
Expand Down
2 changes: 1 addition & 1 deletion addons/escoria-dialog-simple/states/dialog_say_finish.gd
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func enter():
_dialog_manager != null:

if not _dialog_manager.is_connected("say_visible", self, "_on_say_visible"):
_dialog_manager.connect("say_visible", self, "_on_say_visible", [], CONNECT_ONESHOT)
_dialog_manager.connect("say_visible", self, "_on_say_visible")

_dialog_manager.finish()
else:
Expand Down
2 changes: 1 addition & 1 deletion addons/escoria-dialog-simple/states/dialog_visible.gd
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func enter():
escoria.logger.trace(self, "Dialog State Machine: Entered 'visible'.")

if not _dialog_manager.is_connected("say_finished", self, "_on_say_finished"):
_dialog_manager.connect("say_finished", self, "_on_say_finished", [], CONNECT_ONESHOT)
_dialog_manager.connect("say_finished", self, "_on_say_finished")


func handle_input(_event):
Expand Down
Loading

0 comments on commit 04121b0

Please sign in to comment.