Skip to content

Commit

Permalink
feat: Made dialogs pluggable (#424)
Browse files Browse the repository at this point in the history
Co-authored-by: Dennis Ploeger <[email protected]>
  • Loading branch information
dploeger and dploeger authored Oct 27, 2021
1 parent 29dffbc commit 57ce7fb
Show file tree
Hide file tree
Showing 26 changed files with 629 additions and 301 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/fanout.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ jobs:
repo: "godot-escoria/escoria-ui-9verbs"
- dir: "addons/escoria-core"
repo: "godot-escoria/escoria-game-template"
- dir: "addons/escoria-dialog-simple"
repo: "godot-escoria/escoria-dialog-simple"
name: "Fanout ${{ matrix.parts.dir }} to ${{ matrix.parts.repo }}"
runs-on: "ubuntu-latest"
env:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ func run(command_params: Array) -> int:
)

escoria.event_manager.interrupt_running_event()
if escoria.dialog_player:
escoria.dialog_player.interrupt()

if !command_params[1]:
escoria.main.scene_transition.transition(
Expand Down
55 changes: 26 additions & 29 deletions addons/escoria-core/game/core-scripts/esc/commands/say.gd
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# `say object text [type] [avatar]`
# `say player text [type]`
#
# Runs the specified string as a dialog said by the object. Blocks execution
# Runs the specified string as a dialog said by the player. Blocks execution
# until the dialog finishes playing.
#
# The text supports translation keys by prepending the key and separating
Expand All @@ -11,8 +11,6 @@
# Optional parameters:
#
# * "type" determines the type of dialog UI to use. Default value is "default"
# * "avatar" determines the avatar to use for the dialog. Default value is
# "default"
#
# @ESC
extends ESCBaseCommand
Expand All @@ -23,42 +21,41 @@ class_name SayCommand
func configure() -> ESCCommandArgumentDescriptor:
return ESCCommandArgumentDescriptor.new(
2,
[TYPE_STRING, TYPE_STRING, TYPE_STRING, TYPE_STRING],
[TYPE_STRING, TYPE_STRING, TYPE_STRING],
[
null,
null,
ProjectSettings.get_setting("escoria/ui/default_dialog_scene")\
.get_file().get_basename(),
"default"
ProjectSettings.get_setting("escoria/ui/default_dialog_type")
]
)


# Run the command
func run(command_params: Array) -> int:

var dict: Dictionary
var dialog_scene_name = ProjectSettings.get_setting(
"escoria/ui/default_dialog_scene"
)

if dialog_scene_name.empty():
# Validate wether the given arguments match the command descriptor
func validate(arguments: Array):
if not escoria.object_manager.objects.has(arguments[0]):
escoria.logger.report_errors(
"anim: invalid object",
[
"Object with global id %s not found." % arguments[0]
]
)
return false
if ProjectSettings.get_setting("escoria/ui/default_dialog_type") == "" \
and arguments[2] == "":
escoria.logger.report_errors(
"say()",
[
"Project setting 'escoria/ui/default_dialog_scene' is not set.",
"Please set a default dialog scene."
"Project setting 'escoria/ui/default_dialog_type' is not set.",
"Please set a default dialog type."
]
)
return ESCExecution.RC_ERROR
var file = dialog_scene_name.get_file()
var extension = dialog_scene_name.get_extension()
dialog_scene_name = file.rstrip("." + extension)
return .validate(arguments)


# Run the command
func run(command_params: Array) -> int:

# Manage specific dialog scene
if command_params.size() > 2:
dialog_scene_name = command_params[2]
var dict: Dictionary

escoria.current_state = escoria.GAME_STATE.DIALOG

Expand All @@ -74,9 +71,9 @@ func run(command_params: Array) -> int:

escoria.dialog_player.say(
command_params[0],
dialog_scene_name,
command_params[2],
command_params[1]
)
yield(escoria.dialog_player, "dialog_line_finished")
yield(escoria.dialog_player, "say_finished")
escoria.current_state = escoria.GAME_STATE.DEFAULT
return ESCExecution.RC_OK
2 changes: 1 addition & 1 deletion addons/escoria-core/game/escoria.gd
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ var resource_cache: ESCResourceCache
var room_terrain

# Dialog player instantiator. This instance is called directly for dialogs.
var dialog_player: ESCDialogsPlayer
var dialog_player: ESCDialogPlayer

# Inventory scene
var inventory
Expand Down
63 changes: 63 additions & 0 deletions addons/escoria-core/game/scenes/dialogs/esc_dialog_manager.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# A base class for dialog plugins to work with Escoria
extends Control
class_name ESCDialogManager


# Emitted when the say function has completed showing the text
signal say_finished

# Emitted when the player has chosen an option
signal option_chosen(option)


# Check wether a specific type is supported by the
# dialog plugin
#
# #### Parameters
# - type: required type
# *Returns* Wether the type is supported or not
func has_type(type: String) -> bool:
return false


# Check wether a specific chooser type is supported by the
# dialog plugin
#
# #### Parameters
# - type: required chooser type
# *Returns* Wether the type is supported or not
func has_chooser_type(type: String) -> bool:
return 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):
pass


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


# Trigger running the dialog faster
func speedup():
pass


# The say command has been interrupted, cancel the dialog display
func interrupt():
pass
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Base class for all dialog options implementations
extends Node
extends Control
class_name ESCDialogOptionsChooser


Expand Down
132 changes: 78 additions & 54 deletions addons/escoria-core/game/scenes/dialogs/esc_dialog_player.gd
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Escoria dialog player
extends ResourcePreloader
class_name ESCDialogsPlayer
extends Node
class_name ESCDialogPlayer


# Emitted when an answer as chosem
Expand All @@ -10,48 +10,43 @@ class_name ESCDialogsPlayer
# - option: The dialog option that was chosen
signal option_chosen(option)

# Emitted when a dialog line was finished
signal dialog_line_finished
# Emitted when a say command finished
signal say_finished


# Wether the player is currently speaking
var is_speaking = false
var is_speaking: bool = false


# Reference to the dialog UI
var _dialog_ui = null

# Reference to the dialog chooser UI
var _dialog_chooser_ui: ESCDialogOptionsChooser = null
# Reference to the currently playing dialog manager
var _dialog_manager: ESCDialogManager = null


# Register the dialog player and load the dialog resources
func _ready():
if !Engine.is_editor_hint():
escoria.dialog_player = self
_dialog_chooser_ui = ResourceLoader.load(
ProjectSettings.get_setting("escoria/ui/dialogs_chooser")
).instance()
assert(_dialog_chooser_ui is ESCDialogOptionsChooser)
_dialog_chooser_ui.connect(
"option_chosen",
self,
"play_dialog_option_chosen"
)
get_parent().call_deferred("add_child", _dialog_chooser_ui)


# Trigger the finish fast function on the dialog ui
# Trigger the speedup function in the dialog manager
#
# #### Parameters
#
# - event: The input event
func _input(event):
if event is InputEventMouseButton and \
event.pressed:
finish_fast()
speedup()


# Find the matching voice output file for the given key
#
# #### Parameters
#
# - key: Text key provided
# - start: Starting folder to search for voices
#
# *Returns* The path to the matching voice file
func _get_voice_file(key: String, start: String = "") -> String:
if start == "":
start = ProjectSettings.get("escoria/sound/speech_folder")
Expand All @@ -77,63 +72,92 @@ func _get_voice_file(key: String, start: String = "") -> String:
return ""


# A short one line dialog
# Make a character say a text
#
# #### Parameters
#
# - character: Character that is talking
# - ui: UI to use for the dialog
# - line: Line to say
func say(character: String, ui: String, line: String) -> void:
# - type: UI to use for the dialog
# - text: Text to say
func say(character: String, type: String, text: String) -> void:
is_speaking = true
_dialog_ui = get_resource(ui).instance()
get_parent().add_child(_dialog_ui)
var _key_line = line.split(":")
if _key_line.size() == 1:
line = _key_line[0]
elif _key_line.size() >= 2:
var _speech_resource = _get_voice_file(_key_line[0])
for _manager_class in ProjectSettings.get_setting(
"escoria/ui/dialog_managers"
):
if ResourceLoader.exists(_manager_class):
var _manager: ESCDialogManager = load(_manager_class).new()
if _manager.has_type(type):
_dialog_manager = _manager

if _dialog_manager == null:
escoria.logger.report_errors(
"esc_dialog_player.gd: Unknown type",
[
"No dialog manager supports the type %s" % type
]
)
var _key_text = text.split(":")
if _key_text.size() == 1:
text = _key_text[0]
elif _key_text.size() >= 2:
var _speech_resource = _get_voice_file(_key_text[0])
if _speech_resource != "":
(
escoria.object_manager.get_object("_speech").node\
as ESCSpeechPlayer
).set_state(_speech_resource)
line = tr(_key_line[0])
_dialog_ui.say(character, line)
yield(_dialog_ui, "dialog_line_finished")
text = tr(_key_text[0])

_dialog_manager.say(self, character, text, type)
yield(_dialog_manager, "say_finished")
is_speaking = false
emit_signal("dialog_line_finished")
emit_signal("say_finished")


# Called when a dialog line is skipped
func finish_fast() -> void:
if is_speaking and\
escoria.inputs_manager.input_mode != escoria.inputs_manager.INPUT_NONE:
_dialog_ui.finish_fast()
func speedup() -> void:
if is_speaking and escoria.inputs_manager.input_mode != \
escoria.inputs_manager.INPUT_NONE and \
_dialog_manager != null:
_dialog_manager.speedup()


# Display a list of choices
#
# #### Parameters
#
# - dialog: The dialog to start
func start_dialog_choices(dialog: ESCDialog):
func start_dialog_choices(dialog: ESCDialog, type: String = "simple"):
if dialog.options.empty():
escoria.logger.report_errors(
"dialog_player.gd:start_dialog_choices()",
["Received answers array was empty."]
)
_dialog_chooser_ui.set_dialog(dialog)
_dialog_chooser_ui.show_chooser()


# Called when an option was chosen and emits the option_chosen signal
#
# #### Parameters
#
# - option: Option, that was chosen.
func play_dialog_option_chosen(option: ESCDialogOption):

var _dialog_chooser_ui: ESCDialogManager = null

for _manager_class in ProjectSettings.get_setting(
"escoria/ui/dialog_managers"
):
if ResourceLoader.exists(_manager_class):
var _manager: ESCDialogManager = load(_manager_class).new()
if _manager.has_chooser_type(type):
_dialog_chooser_ui = _manager

if _dialog_chooser_ui == null:
escoria.logger.report_errors(
"esc_dialog_player.gd: Unknown chooser type",
[
"No dialog manager supports the chooser type %s" % type
]
)

_dialog_chooser_ui.choose(self, dialog)
var option = yield(_dialog_chooser_ui, "option_chosen")
emit_signal("option_chosen", option)
_dialog_chooser_ui.hide_chooser()


# Interrupt the currently running dialog
func interrupt():
if _dialog_manager != null:
_dialog_manager.interrupt()
Loading

0 comments on commit 57ce7fb

Please sign in to comment.