Updated escoria-demo-game

This commit is contained in:
2023-06-06 19:28:49 +02:00
parent 6d3bd3511c
commit 0d38a8646a
116 changed files with 1670 additions and 1291 deletions

View File

@@ -46,13 +46,35 @@ 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
#
# #### 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):
# - type: The dialog chooser type to use
func choose(dialog_player: Node, dialog: ESCDialog, type: String):
pass

View File

@@ -1,5 +1,5 @@
# Escoria dialog player
extends StateMachine
extends Node
class_name ESCDialogPlayer
@@ -14,8 +14,20 @@ signal option_chosen(option)
signal say_finished
# Reference to the currently playing dialog manager
var _dialog_manager: ESCDialogManager = null
# Used when specifying dialog types in various methods
const DIALOG_TYPE_SAY = "say"
const DIALOG_TYPE_CHOOSE = "choose"
# Reference to the currently playing "say" dialog manager
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
@@ -25,38 +37,27 @@ func _ready():
escoria.dialog_player = self
_create_states()
_add_states_to_machine()
states_map["say"].connect("dialog_manager_set", self, "_on_dialog_manager_set")
current_state_name = "idle"
START_STATE = states_map[current_state_name]
initialize(START_STATE)
# 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
# Creates the states for this state machine.
func _create_states() -> void:
states_map = {
"idle": DialogIdle.new(),
"say": DialogSay.new(),
"say_fast": DialogSayFast.new(),
"say_finish": DialogSayFinish.new(),
"visible": DialogVisible.new(),
"finish": DialogFinish.new(),
"interrupt": DialogInterrupt.new(),
"choices": DialogChoices.new(),
}
# This state needs a reference to this class.
states_map["finish"].initialize(self)
# Adds any created states into the state machine as children.
func _add_states_to_machine() -> void:
for key in states_map:
add_child(states_map[key])
# 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
@@ -67,18 +68,19 @@ func _add_states_to_machine() -> void:
# - type: UI to use for the dialog
# - text: Text to say
func say(character: String, type: String, text: String) -> void:
states_map["say"].initialize(character, type, text)
_change_state("say")
if type == "":
type = ESCProjectSettingsManager.get_setting(
ESCProjectSettingsManager.DEFAULT_DIALOG_TYPE
)
# We only need to remove the dialog manager from the scene tree if the dialog manager type
# has changed since the last use of this method.
_update_dialog_manager(DIALOG_TYPE_SAY, _say_dialog_manager, type)
# Called when a dialogue line is to be sped up.
func speedup() -> void:
_change_state("say_fast")
if _block_say_enabled:
_say_dialog_manager.enable_preserve_dialog_box()
# Called when a dialogue line is to be finished immediately.
func finish() -> void:
_change_state("say_finish")
_say_dialog_manager.say(self, character, text, type)
# Display a list of choices
@@ -86,22 +88,111 @@ func finish() -> void:
# #### Parameters
#
# - dialog: The dialog to start
# - type: The dialog chooser type to use (default: "simple")
func start_dialog_choices(dialog: ESCDialog, type: String = "simple"):
states_map["choices"].initialize(self, dialog, type)
_change_state("choices")
# We only need to remove the dialog manager from the scene tree if the dialog manager type
# has changed since the last use of this method.
_update_dialog_manager(DIALOG_TYPE_CHOOSE, _choose_dialog_manager, type)
_choose_dialog_manager.choose(self, dialog, type)
# Interrupt the currently running dialog
func interrupt() -> void:
_change_state("interrupt")
if is_instance_valid(_say_dialog_manager):
_say_dialog_manager.interrupt()
# Since the dialog manager is determined when a `say` command is performed and
# other states need to know which one was picked, we notify the necessary states
# via this method.
func _on_dialog_manager_set(dialog_manager: ESCDialogManager) -> void:
_dialog_manager = dialog_manager
states_map["say_fast"].initialize(dialog_manager)
states_map["say_finish"].initialize(dialog_manager)
states_map["visible"].initialize(dialog_manager)
states_map["interrupt"].initialize(dialog_manager)
# Loads the first dialog manager that supports the specified "say" type; otherwise,
# the engine throws an error and stops.
#
# #### Parameters
# - type: The type the dialog manager should support, e.g. "floating"
func _determine_say_dialog_manager(type: String) -> void:
var dialog_manager: ESCDialogManager = null
for _manager_class in ESCProjectSettingsManager.get_setting(
ESCProjectSettingsManager.DIALOG_MANAGERS
):
if ResourceLoader.exists(_manager_class):
var _manager: ESCDialogManager = load(_manager_class).new()
if _manager.has_type(type):
dialog_manager = _manager
else:
dialog_manager = null
if not is_instance_valid(dialog_manager):
escoria.logger.error(
self,
"No dialog manager called '%s' configured." % type
)
_say_dialog_manager = dialog_manager
# Loads the first dialog manager that supports the specified "choose" type; otherwise,
# the engine throws an error and stops.
#
# #### Parameters
# - type: The type the dialog manager should support, e.g. "simple"
func _determine_choose_dialog_manager(type: String) -> void:
var dialog_manager: ESCDialogManager = null
for _manager_class in ESCProjectSettingsManager.get_setting(
ESCProjectSettingsManager.DIALOG_MANAGERS
):
if ResourceLoader.exists(_manager_class):
var _manager: ESCDialogManager = load(_manager_class).new()
if _manager.has_chooser_type(type):
dialog_manager = _manager
else:
dialog_manager = null
if not is_instance_valid(dialog_manager):
escoria.logger.error(
self,
"No dialog manager called '%s' configured." % type
)
_choose_dialog_manager = dialog_manager
# If necessary, updates the dialog manager for the specified dialog type.
#
# #### Parameters
#
# - dialog_type: The type of dialog that will be managed, e.g. "say" or "choose"
# - current_dialog_manager: The dialog manager currently being used (if any) for the specified
# dialog type
# - dialog_manager_type: The dialog manager type specific to the dialog manager being requested
func _update_dialog_manager(dialog_type: String, current_dialog_manager: ESCDialogManager, \
dialog_manager_type: String) -> void:
if is_instance_valid(current_dialog_manager):
if not current_dialog_manager.has_type(dialog_manager_type):
if is_a_parent_of(current_dialog_manager):
remove_child(current_dialog_manager)
add_child(_determine_dialog_manager(dialog_type, dialog_manager_type))
else:
add_child(_determine_dialog_manager(dialog_type, dialog_manager_type))
# Sets the requested dialog manager type for the specified dialog function.
#
# #### Parameters
#
# - dialog_type: The type of dialog that will be managed, e.g. "say" or "choose"
# - dialog_manager_type: The dialog manager type specific to the dialog manager being requested
#
# *Returns* the newly-resolved dialog manager
func _determine_dialog_manager(dialog_type: String, dialog_manager_type: String) -> ESCDialogManager:
if dialog_type == DIALOG_TYPE_SAY:
_determine_say_dialog_manager(dialog_manager_type)
return _say_dialog_manager
elif dialog_type == DIALOG_TYPE_CHOOSE:
_determine_choose_dialog_manager(dialog_manager_type)
return _choose_dialog_manager
# This line will never be hit as a failure above will result in an Escoria error
return null

View File

@@ -1,58 +0,0 @@
extends State
class_name DialogChoices
# The owning dialog player.
var _dialog_player
# The dialog to start.
var _dialog: ESCDialog
var _type: String = "simple"
var _dialog_chooser_ui: ESCDialogManager = null
var _ready_to_choose: bool
func initialize(dialog_player, dialog: ESCDialog, type: String) -> void:
_dialog_player = dialog_player
_dialog = dialog
_type = type
func enter():
escoria.logger.trace(self, "Dialog State Machine: Entered 'choices'.")
if _dialog.options.empty():
escoria.logger.error(
self,
"Received dialog options array was empty."
)
for _manager_class in ESCProjectSettingsManager.get_setting(
ESCProjectSettingsManager.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.error(
self,
"No dialog manager supports the chooser type %s." % _type
)
_ready_to_choose = true
func update(_delta):
if _ready_to_choose:
_ready_to_choose = false
_dialog_chooser_ui.choose(self, _dialog)
var option = yield(_dialog_chooser_ui, "option_chosen")
escoria.logger.trace(self, "Dialog State Machine: 'choices' -> 'idle'")
emit_signal("finished", "idle")
_dialog_player.emit_signal("option_chosen", option)

View File

@@ -1,20 +0,0 @@
extends State
class_name DialogFinish
# Owning dialog player
var _dialog_player
func initialize(dialog_player) -> void:
_dialog_player = dialog_player
func enter():
escoria.logger.trace(self, "Dialog State Machine: Entered 'finish'.")
func update(_delta):
escoria.logger.trace(self, "Dialog State Machine: 'finish' -> 'idle'")
emit_signal("finished", "idle")
_dialog_player.emit_signal("say_finished")

View File

@@ -1,6 +0,0 @@
extends State
class_name DialogIdle
func enter():
escoria.logger.trace(self, "Dialog State Machine: Entered 'idle'.")

View File

@@ -1,26 +0,0 @@
extends State
class_name DialogInterrupt
# Reference to the currently playing dialog manager
var _dialog_manager: ESCDialogManager = null
func initialize(dialog_manager: ESCDialogManager) -> void:
_dialog_manager = dialog_manager
func enter():
escoria.logger.trace(self, "Dialog State Machine: Entered 'interrupt'.")
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.interrupt()
func _on_say_finished() -> void:
escoria.logger.trace(self, "Dialog State Machine: 'interrupt' -> 'finish'")
emit_signal("finished", "finish")

View File

@@ -1,205 +0,0 @@
extends State
class_name DialogSay
signal dialog_manager_set(dialog_manager)
# A regular expression that separates the translation key from the text
const KEYTEXT_REGEX = "^((?<key>[^:]+):)?\"(?<text>.+)\""
# Reference to the currently playing dialog manager
var _dialog_manager: ESCDialogManager = null
# Character that is talking
var _character: String
# UI to use for the dialog
var _type: String
# Text to say
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 _stop_talking_animation_on_option: String
# Constructor
func _init() -> void:
_keytext_regex.compile(KEYTEXT_REGEX)
func initialize(character: String, type: String, text: String) -> void:
_character = character
_type = type
_text = text
_stop_talking_animation_on_option = \
ESCProjectSettingsManager.get_setting(SimpleDialogPlugin.STOP_TALKING_ANIMATION_ON)
func handle_input(_event):
if _event is InputEventMouseButton and _event.pressed:
if escoria.inputs_manager.input_mode != \
escoria.inputs_manager.INPUT_NONE and \
_dialog_manager != null:
var left_click_action = ESCProjectSettingsManager.get_setting(SimpleDialogPlugin.LEFT_CLICK_ACTION)
_handle_left_click_action(left_click_action)
func _handle_left_click_action(left_click_action: String) -> void:
match left_click_action:
SimpleDialogPlugin.LEFT_CLICK_ACTION_SPEED_UP:
if _dialog_manager.is_connected("say_visible", self, "_on_say_visible"):
_dialog_manager.disconnect("say_visible", self, "_on_say_visible")
escoria.logger.trace(self, "Dialog State Machine: 'say' -> 'say_fast'")
emit_signal("finished", "say_fast")
SimpleDialogPlugin.LEFT_CLICK_ACTION_INSTANT_FINISH:
if _dialog_manager.is_connected("say_visible", self, "_on_say_visible"):
_dialog_manager.disconnect("say_visible", self, "_on_say_visible")
escoria.logger.trace(self, "Dialog State Machine: 'say' -> 'say_finish'")
emit_signal("finished", "say_finish")
get_tree().set_input_as_handled()
func enter():
escoria.logger.trace(self, "Dialog State Machine: Entered 'say'.")
if _type == "":
_type = ESCProjectSettingsManager.get_setting(
ESCProjectSettingsManager.DEFAULT_DIALOG_TYPE
)
var dialog_manager: ESCDialogManager = null
for _manager_class in ESCProjectSettingsManager.get_setting(
ESCProjectSettingsManager.DIALOG_MANAGERS
):
if ResourceLoader.exists(_manager_class):
var _manager: ESCDialogManager = load(_manager_class).new()
if _manager.has_type(_type):
dialog_manager = _manager
else:
dialog_manager = null
if dialog_manager == null:
escoria.logger.error(
self,
"No dialog manager called '%s' configured." % _type
)
_dialog_manager = dialog_manager
emit_signal("dialog_manager_set", dialog_manager)
if not _dialog_manager.is_connected("say_visible", self, "_on_say_visible"):
_dialog_manager.connect("say_visible", self, "_on_say_visible", [], CONNECT_ONESHOT)
var matches = _keytext_regex.search(_text)
if not matches:
escoria.logger.error(
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 == "":
escoria.logger.warn(
self,
"Unable to find voice file with key '%s'." % matches.get_string("key")
)
else:
(
escoria.object_manager.get_object(escoria.object_manager.SPEECH).node\
as ESCSpeechPlayer
).set_state(_speech_resource)
if _stop_talking_animation_on_option == SimpleDialogPlugin.STOP_TALKING_ANIMATION_ON_END_OF_AUDIO:
(
escoria.object_manager.get_object(escoria.object_manager.SPEECH).node\
as ESCSpeechPlayer
).stream.connect("finished", self, "_on_audio_finished", [], CONNECT_ONESHOT)
var translated_text: String = tr(matches.get_string("key"))
# Only update the text if the translated text was found; otherwise, raise
# a warning and use the original, untranslated text.
if translated_text == matches.get_string("key"):
escoria.logger.warn(
self,
"Unable to find translation key '%s'. Using untranslated text." % matches.get_string("key")
)
_text = matches.get_string("text")
else:
_text = translated_text
else:
_text = matches.get_string("text")
_ready_to_say = true
func update(_delta):
if _ready_to_say:
_dialog_manager.say(self, _character, _text, _type)
_ready_to_say = false
# 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 = ESCProjectSettingsManager.get_setting(
ESCProjectSettingsManager.SPEECH_FOLDER
)
var _dir = Directory.new()
if _dir.open(start) == OK:
_dir.list_dir_begin(true, true)
var file_name = _dir.get_next()
while file_name != "":
if _dir.current_is_dir():
var _voice_file = _get_voice_file(
key,
start.plus_file(file_name)
)
if _voice_file != "":
return _voice_file
else:
if file_name == "%s.%s.import" % [
key,
ESCProjectSettingsManager.get_setting(
ESCProjectSettingsManager.SPEECH_EXTENSION
)
]:
return start.plus_file(file_name.trim_suffix(".import"))
file_name = _dir.get_next()
return ""
func _on_say_visible() -> void:
escoria.logger.trace(self, "Dialog State Machine: 'say' -> 'visible'")
emit_signal("finished", "visible")
func _on_audio_finished() -> void:
_dialog_manager.voice_audio_finished()

View File

@@ -1,30 +0,0 @@
extends State
class_name DialogSayFast
# Reference to the currently playing dialog manager
var _dialog_manager: ESCDialogManager = null
func initialize(dialog_manager: ESCDialogManager) -> void:
_dialog_manager = dialog_manager
func enter():
escoria.logger.trace(self, "Dialog State Machine: Entered 'say_fast'.")
if escoria.inputs_manager.input_mode != \
escoria.inputs_manager.INPUT_NONE and \
_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.speedup()
else:
escoria.logger.error(self, "Illegal state.")
func _on_say_visible() -> void:
escoria.logger.trace(self, "Dialog State Machine: 'say_fast' -> 'visible'")
emit_signal("finished", "visible")

View File

@@ -1,30 +0,0 @@
extends State
class_name DialogSayFinish
# Reference to the currently playing dialog manager
var _dialog_manager: ESCDialogManager = null
func initialize(dialog_manager: ESCDialogManager) -> void:
_dialog_manager = dialog_manager
func enter():
escoria.logger.trace(self, "Dialog State Machine: Entered 'say_finish'.")
if escoria.inputs_manager.input_mode != \
escoria.inputs_manager.INPUT_NONE and \
_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.finish()
else:
escoria.logger.error(self, "Illegal state.")
func _on_say_visible() -> void:
escoria.logger.trace(self, "Dialog State Machine: 'say_finish' -> 'visible'")
emit_signal("finished", "visible")

View File

@@ -1,35 +0,0 @@
extends State
class_name DialogVisible
# Reference to the currently playing dialog manager
var _dialog_manager: ESCDialogManager = null
func initialize(dialog_manager: ESCDialogManager) -> void:
_dialog_manager = dialog_manager
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)
func handle_input(_event):
if _event is InputEventMouseButton and _event.pressed:
if escoria.inputs_manager.input_mode != \
escoria.inputs_manager.INPUT_NONE:
if _dialog_manager.is_connected("say_finished", self, "_on_say_finished"):
_dialog_manager.disconnect("say_finished", self, "_on_say_finished")
emit_signal("finished", "interrupt")
get_tree().set_input_as_handled()
# Handles the end of a say function after it has emitted say_finished.
func _on_say_finished():
escoria.logger.trace(self, "Dialog State Machine: 'visible' -> 'finish'")
emit_signal("finished", "finish")