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

@@ -1,14 +1,28 @@
# A simple dialog manager for Escoria
extends ESCDialogManager
class_name ESCDialogSimple
# State machine that governs how the dialog manager behaves
var state_machine = preload("res://addons/escoria-dialog-simple/esc_dialog_simple_state_machine.gd").new()
# The currently running player
var _type_player: Node = null
var _preserved_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)
# Check whether a specific type is supported by the
# dialog plugin
@@ -30,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)
_preserved_type_player_type = ""
# Output a text said by the item specified by the global id. Emit
# `say_finished` after finishing displaying the text.
#
@@ -42,6 +81,40 @@ func has_chooser_type(type: String) -> bool:
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")
# yield(_type_player, "say_finished")
# if _dialog_player.get_children().has(_type_player):
# _dialog_player.remove_child(_type_player)
# emit_signal("say_finished")
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, 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"\
@@ -51,21 +124,26 @@ func say(dialog_player: Node, global_id: String, text: String, type: String):
"res://addons/escoria-dialog-simple/types/avatar.tscn"\
).instance()
_type_player.connect("say_finished", self, "_on_say_finished", [], CONNECT_ONESHOT)
_type_player.connect("say_visible", self, "_on_say_visible", [], CONNECT_ONESHOT)
_type_player.connect("say_finished", self, "_on_say_finished")
_type_player.connect("say_visible", self, "_on_say_visible")
_dialog_player.add_child(_type_player)
_type_player.say(global_id, text)
# yield(_type_player, "say_finished")
# if _dialog_player.get_children().has(_type_player):
# _dialog_player.remove_child(_type_player)
# emit_signal("say_finished")
func _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():
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)
emit_signal("say_finished")
_is_saying = false
emit_signal("say_finished")
func _on_say_visible():
@@ -78,13 +156,26 @@ func _on_say_visible():
# #### Parameters
# - dialog_player: Node of the dialog player in the UI
# - dialog: Information about the dialog to display
func choose(dialog_player: Node, dialog: ESCDialog):
var chooser = preload(\
"res://addons/escoria-dialog-simple/chooser/simple.tscn"\
).instance()
# - type: The dialog chooser type to use
func choose(dialog_player: Node, dialog: ESCDialog, type: String):
_dialog_player = dialog_player
state_machine.states_map["choices"].initialize(dialog_player, self, dialog, type)
state_machine._change_state("choices")
func do_choose(dialog_player: Node, dialog: ESCDialog, type: String = "simple"):
var chooser
if type == "simple" or type == "":
chooser = preload(\
"res://addons/escoria-dialog-simple/chooser/simple.tscn"\
).instance()
dialog_player.add_child(chooser)
chooser.set_dialog(dialog)
chooser.show_chooser()
var option = yield(chooser, "option_chosen")
dialog_player.remove_child(chooser)
emit_signal("option_chosen", option)
@@ -92,13 +183,13 @@ func choose(dialog_player: Node, dialog: ESCDialog):
# Trigger running the dialogue faster
func speedup():
if _type_player != null:
if is_instance_valid(_type_player):
_type_player.speedup()
# Trigger an instant finish of the current dialog
func finish():
if _type_player != null:
if is_instance_valid(_type_player):
_type_player.finish()
@@ -110,11 +201,13 @@ 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")
# To be called if voice audio has finished.
func voice_audio_finished():
if _type_player != null:
if is_instance_valid(_type_player):
_type_player.voice_audio_finished()

View File

@@ -0,0 +1,20 @@
extends Resource
class_name SimpleDialogSettings
const SETTINGS_ROOT = "escoria/dialog_simple"
const AVATARS_PATH = "%s/avatars_path" % SETTINGS_ROOT
const TEXT_TIME_PER_LETTER_MS = "%s/text_time_per_letter_ms" % SETTINGS_ROOT
const TEXT_TIME_PER_LETTER_MS_FAST = "%s/text_time_per_fast_letter_ms" % SETTINGS_ROOT
const READING_SPEED_IN_WPM = "%s/reading_speed_in_wpm" % SETTINGS_ROOT
const CLEAR_TEXT_BY_CLICK_ONLY = "%s/clear_text_by_click_only" % SETTINGS_ROOT
const LEFT_CLICK_ACTION = "%s/left_click_action" % SETTINGS_ROOT
const STOP_TALKING_ANIMATION_ON = "%s/stop_talking_animation_on" % SETTINGS_ROOT
const LEFT_CLICK_ACTION_SPEED_UP = "Speed up"
const LEFT_CLICK_ACTION_INSTANT_FINISH = "Instant finish"
const LEFT_CLICK_ACTION_NOTHING = "None"
const STOP_TALKING_ANIMATION_ON_END_OF_TEXT = "End of text"
const STOP_TALKING_ANIMATION_ON_END_OF_AUDIO = "End of audio"

View File

@@ -0,0 +1,31 @@
extends "res://addons/escoria-dialog-simple/patterns/state_machine/state_machine.gd"
func _init():
_create_states()
_add_states_to_machine()
current_state_name = "idle"
START_STATE = states_map[current_state_name]
initialize(START_STATE)
# Creates the states for this state machine.
func _create_states() -> void:
states_map = {
"idle": preload("res://addons/escoria-dialog-simple/states/dialog_idle.gd").new(),
"say": preload("res://addons/escoria-dialog-simple/states/dialog_say.gd").new(),
"say_fast": preload("res://addons/escoria-dialog-simple/states/dialog_say_fast.gd").new(),
"say_finish": preload("res://addons/escoria-dialog-simple/states/dialog_say_finish.gd").new(),
"visible": preload("res://addons/escoria-dialog-simple/states/dialog_visible.gd").new(),
"finish": preload("res://addons/escoria-dialog-simple/states/dialog_finish.gd").new(),
"interrupt": preload("res://addons/escoria-dialog-simple/states/dialog_interrupt.gd").new(),
"choices": preload("res://addons/escoria-dialog-simple/states/dialog_choices.gd").new(),
}
# 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])

View File

@@ -0,0 +1,31 @@
"""
Base interface for all states: it doesn't do anything in itself
but forces us to pass the right arguments to the methods below
and makes sure every State object had all of these methods.
"""
extends Node
signal finished(next_state_name)
# Initialize the state. E.g. change the animation
func enter():
return
# Clean up the state. Reinitialize values like a timer
func exit():
return
func handle_input(_event):
return
func update(_delta):
return
func _on_animation_finished(_anim_name):
return

View File

@@ -0,0 +1,92 @@
"""
Base interface for a generic state machine
It handles initializing, setting the machine active or not
delegating _physics_process, _input calls to the State nodes,
and changing the current/active state.
"""
extends Node
signal state_changed(current_state)
"""
You must set a starting node from the inspector or on
the node that inherits from this state machine interface
If you don't the game will crash (on purpose, so you won't
forget to initialize the state machine)
"""
export(NodePath) var START_STATE
var states_map = {}
var states_stack = [] # can also be used as a pushdown automaton
var current_state = null
var current_state_name = ""
var _active = false setget set_active
func initialize(start_state):
for child in get_children():
child.connect("finished", self, "_change_state")
set_active(true)
states_stack.push_front(start_state)
current_state = states_stack[0]
current_state.enter()
func set_active(value):
_active = value
set_physics_process(value)
set_process_input(value)
if not _active:
states_stack = []
current_state = null
func _input(event):
current_state.handle_input(event)
func _physics_process(delta):
current_state.update(delta)
func _on_animation_finished(anim_name):
if not _active:
return
current_state._on_animation_finished(anim_name)
func _change_state(state_name):
if not _active:
return
escoria.logger.trace(
self,
"Dialog State Machine: Changing state from '%s' to '%s'." % [current_state_name, state_name]
)
current_state.exit()
if state_name == "previous":
states_stack.pop_front()
else:
states_stack[0] = states_map[state_name]
current_state = states_stack[0]
emit_signal("state_changed", current_state)
#if state_name != "previous":
current_state.enter()
current_state_name = state_name
func get_current_state_name():
for key in states_map.keys():
if states_map[key] == current_state:
return key
return null

View File

@@ -1,26 +1,8 @@
# A simple dialog manager for Escoria
tool
extends EditorPlugin
class_name SimpleDialogPlugin
const MANAGER_CLASS="res://addons/escoria-dialog-simple/esc_dialog_simple.gd"
const SETTINGS_ROOT="escoria/dialog_simple"
const AVATARS_PATH = "%s/avatars_path" % SETTINGS_ROOT
const TEXT_TIME_PER_LETTER_MS = "%s/text_time_per_letter_ms" % SETTINGS_ROOT
const TEXT_TIME_PER_LETTER_MS_FAST = "%s/text_time_per_fast_letter_ms" % SETTINGS_ROOT
const READING_SPEED_IN_WPM = "%s/reading_speed_in_wpm" % SETTINGS_ROOT
const CLEAR_TEXT_BY_CLICK_ONLY = "%s/clear_text_by_click_only" % SETTINGS_ROOT
const LEFT_CLICK_ACTION = "%s/left_click_action" % SETTINGS_ROOT
const STOP_TALKING_ANIMATION_ON = "%s/stop_talking_animation_on" % SETTINGS_ROOT
const LEFT_CLICK_ACTION_SPEED_UP = "Speed up"
const LEFT_CLICK_ACTION_INSTANT_FINISH = "Instant finish"
const LEFT_CLICK_ACTION_NOTHING = "None"
const STOP_TALKING_ANIMATION_ON_END_OF_TEXT = "End of text"
const STOP_TALKING_ANIMATION_ON_END_OF_AUDIO = "End of audio"
const MANAGER_CLASS = "res://addons/escoria-dialog-simple/esc_dialog_simple.gd"
const READING_SPEED_IN_WPM_DEFAULT_VALUE = 200
const TEXT_TIME_PER_LETTER_MS_DEFAULT_VALUE = 100
@@ -28,14 +10,14 @@ const TEXT_TIME_PER_LETTER_MS_FAST_DEFAULT_VALUE = 25
var left_click_actions: PoolStringArray = [
LEFT_CLICK_ACTION_SPEED_UP,
LEFT_CLICK_ACTION_INSTANT_FINISH,
LEFT_CLICK_ACTION_NOTHING
SimpleDialogSettings.LEFT_CLICK_ACTION_SPEED_UP,
SimpleDialogSettings.LEFT_CLICK_ACTION_INSTANT_FINISH,
SimpleDialogSettings.LEFT_CLICK_ACTION_NOTHING
]
var stop_talking_animation_on_options: PoolStringArray = [
STOP_TALKING_ANIMATION_ON_END_OF_TEXT,
STOP_TALKING_ANIMATION_ON_END_OF_AUDIO
SimpleDialogSettings.STOP_TALKING_ANIMATION_ON_END_OF_TEXT,
SimpleDialogSettings.STOP_TALKING_ANIMATION_ON_END_OF_AUDIO
]
@@ -52,31 +34,31 @@ func disable_plugin():
)
ESCProjectSettingsManager.remove_setting(
AVATARS_PATH
SimpleDialogSettings.AVATARS_PATH
)
ESCProjectSettingsManager.remove_setting(
TEXT_TIME_PER_LETTER_MS
SimpleDialogSettings.TEXT_TIME_PER_LETTER_MS
)
ESCProjectSettingsManager.remove_setting(
TEXT_TIME_PER_LETTER_MS_FAST
SimpleDialogSettings.TEXT_TIME_PER_LETTER_MS_FAST
)
ESCProjectSettingsManager.remove_setting(
CLEAR_TEXT_BY_CLICK_ONLY
SimpleDialogSettings.CLEAR_TEXT_BY_CLICK_ONLY
)
ESCProjectSettingsManager.remove_setting(
READING_SPEED_IN_WPM
SimpleDialogSettings.READING_SPEED_IN_WPM
)
ESCProjectSettingsManager.remove_setting(
LEFT_CLICK_ACTION
SimpleDialogSettings.LEFT_CLICK_ACTION
)
ESCProjectSettingsManager.remove_setting(
STOP_TALKING_ANIMATION_ON
SimpleDialogSettings.STOP_TALKING_ANIMATION_ON
)
EscoriaPlugin.deregister_dialog_manager(MANAGER_CLASS)
@@ -96,7 +78,7 @@ func enable_plugin():
)
ESCProjectSettingsManager.register_setting(
AVATARS_PATH,
SimpleDialogSettings.AVATARS_PATH,
"res://game/dialog_avatars",
{
"type": TYPE_STRING,
@@ -105,7 +87,7 @@ func enable_plugin():
)
ESCProjectSettingsManager.register_setting(
TEXT_TIME_PER_LETTER_MS,
SimpleDialogSettings.TEXT_TIME_PER_LETTER_MS,
TEXT_TIME_PER_LETTER_MS_DEFAULT_VALUE,
{
"type": TYPE_REAL
@@ -113,7 +95,7 @@ func enable_plugin():
)
ESCProjectSettingsManager.register_setting(
TEXT_TIME_PER_LETTER_MS_FAST,
SimpleDialogSettings.TEXT_TIME_PER_LETTER_MS_FAST,
TEXT_TIME_PER_LETTER_MS_FAST_DEFAULT_VALUE,
{
"type": TYPE_REAL
@@ -121,7 +103,7 @@ func enable_plugin():
)
ESCProjectSettingsManager.register_setting(
CLEAR_TEXT_BY_CLICK_ONLY,
SimpleDialogSettings.CLEAR_TEXT_BY_CLICK_ONLY,
false,
{
"type": TYPE_BOOL
@@ -129,7 +111,7 @@ func enable_plugin():
)
ESCProjectSettingsManager.register_setting(
READING_SPEED_IN_WPM,
SimpleDialogSettings.READING_SPEED_IN_WPM,
READING_SPEED_IN_WPM_DEFAULT_VALUE,
{
"type": TYPE_INT
@@ -139,8 +121,8 @@ func enable_plugin():
var left_click_actions_string: String = left_click_actions.join(",")
ESCProjectSettingsManager.register_setting(
LEFT_CLICK_ACTION,
LEFT_CLICK_ACTION_SPEED_UP,
SimpleDialogSettings.LEFT_CLICK_ACTION,
SimpleDialogSettings.LEFT_CLICK_ACTION_SPEED_UP,
{
"type": TYPE_STRING,
"hint": PROPERTY_HINT_ENUM,
@@ -151,8 +133,8 @@ func enable_plugin():
var stop_talking_animation_on_options_string: String = stop_talking_animation_on_options.join(",")
ESCProjectSettingsManager.register_setting(
STOP_TALKING_ANIMATION_ON,
STOP_TALKING_ANIMATION_ON_END_OF_AUDIO,
SimpleDialogSettings.STOP_TALKING_ANIMATION_ON,
SimpleDialogSettings.STOP_TALKING_ANIMATION_ON_END_OF_AUDIO,
{
"type": TYPE_STRING,
"hint": PROPERTY_HINT_ENUM,

View File

@@ -0,0 +1,44 @@
extends "res://addons/escoria-dialog-simple/patterns/state_machine/state.gd"
# 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_chooser_ui: ESCDialogManager, dialog: ESCDialog, type: String) -> void:
_dialog_player = dialog_player
_dialog_chooser_ui = dialog_chooser_ui
_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."
)
_ready_to_choose = true
func update(_delta):
if _ready_to_choose:
_ready_to_choose = false
_dialog_chooser_ui.do_choose(_dialog_player, _dialog, _type)
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

@@ -0,0 +1,19 @@
extends "res://addons/escoria-dialog-simple/patterns/state_machine/state.gd"
# 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

@@ -0,0 +1,5 @@
extends "res://addons/escoria-dialog-simple/patterns/state_machine/state.gd"
func enter():
escoria.logger.trace(self, "Dialog State Machine: Entered 'idle'.")

View File

@@ -0,0 +1,25 @@
extends "res://addons/escoria-dialog-simple/patterns/state_machine/state.gd"
# 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")
_dialog_manager.interrupt()
func _on_say_finished() -> void:
escoria.logger.trace(self, "Dialog State Machine: 'interrupt' -> 'finish'")
emit_signal("finished", "finish")

View File

@@ -0,0 +1,181 @@
extends "res://addons/escoria-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
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(dialog_manager: ESCDialogManager, character: String, text: String, type: String) -> void:
_dialog_manager = dialog_manager
_character = character
_text = text
_type = type
_stop_talking_animation_on_option = \
ESCProjectSettingsManager.get_setting(SimpleDialogSettings.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(SimpleDialogSettings.LEFT_CLICK_ACTION)
_handle_left_click_action(left_click_action)
func _handle_left_click_action(left_click_action: String) -> void:
match left_click_action:
SimpleDialogSettings.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")
SimpleDialogSettings.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 not _dialog_manager.is_connected("say_visible", self, "_on_say_visible"):
_dialog_manager.connect("say_visible", self, "_on_say_visible")
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 == SimpleDialogSettings.STOP_TALKING_ANIMATION_ON_END_OF_AUDIO:
if not (
escoria.object_manager.get_object(escoria.object_manager.SPEECH).node\
as ESCSpeechPlayer
).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"))
# 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.do_say(_character, _text)
_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

@@ -0,0 +1,29 @@
extends "res://addons/escoria-dialog-simple/patterns/state_machine/state.gd"
# 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")
_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

@@ -0,0 +1,29 @@
extends "res://addons/escoria-dialog-simple/patterns/state_machine/state.gd"
# 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")
_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

@@ -0,0 +1,34 @@
extends "res://addons/escoria-dialog-simple/patterns/state_machine/state.gd"
# 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")
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")

View File

@@ -47,7 +47,7 @@ onready var is_paused: bool = true
# Build up the UI
func _ready():
_text_time_per_character = ProjectSettings.get_setting(
SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS
SimpleDialogSettings.TEXT_TIME_PER_LETTER_MS
)
if _text_time_per_character < 0:
@@ -55,15 +55,15 @@ func _ready():
self,
"%s setting must be a non-negative number. Will use default value of %s." %
[
SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS,
SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS_DEFAULT_VALUE
SimpleDialogSettings.TEXT_TIME_PER_LETTER_MS,
SimpleDialogSettings.TEXT_TIME_PER_LETTER_MS_DEFAULT_VALUE
]
)
_text_time_per_character = SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS_DEFAULT_VALUE
_text_time_per_character = SimpleDialogSettings.TEXT_TIME_PER_LETTER_MS_DEFAULT_VALUE
_fast_text_time_per_character = ProjectSettings.get_setting(
SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS_FAST
SimpleDialogSettings.TEXT_TIME_PER_LETTER_MS_FAST
)
if _fast_text_time_per_character < 0:
@@ -71,15 +71,15 @@ func _ready():
self,
"%s setting must be a non-negative number. Will use default value of %s." %
[
SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS_FAST,
SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS_FAST_DEFAULT_VALUE
SimpleDialogSettings.TEXT_TIME_PER_LETTER_MS_FAST,
SimpleDialogSettings.TEXT_TIME_PER_LETTER_MS_FAST_DEFAULT_VALUE
]
)
_fast_text_time_per_character = SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS_FAST_DEFAULT_VALUE
_fast_text_time_per_character = SimpleDialogSettings.TEXT_TIME_PER_LETTER_MS_FAST_DEFAULT_VALUE
_reading_speed_in_wpm = ProjectSettings.get_setting(
SimpleDialogPlugin.READING_SPEED_IN_WPM
SimpleDialogSettings.READING_SPEED_IN_WPM
)
if _reading_speed_in_wpm <= 0:
@@ -87,12 +87,12 @@ func _ready():
self,
"%s setting must be a positive number. Will use default value of %s." %
[
SimpleDialogPlugin.READING_SPEED_IN_WPM,
SimpleDialogPlugin.READING_SPEED_IN_WPM_DEFAULT_VALUE
SimpleDialogSettings.READING_SPEED_IN_WPM,
SimpleDialogSettings.READING_SPEED_IN_WPM_DEFAULT_VALUE
]
)
_reading_speed_in_wpm = SimpleDialogPlugin.READING_SPEED_IN_WPM_DEFAULT_VALUE
_reading_speed_in_wpm = SimpleDialogSettings.READING_SPEED_IN_WPM_DEFAULT_VALUE
_word_regex.compile("\\S+")
@@ -106,6 +106,8 @@ func _ready():
escoria.connect("paused", self, "_on_paused")
escoria.connect("resumed", self, "_on_resumed")
connect("tree_exited", self, "_on_tree_exited")
# Switch the current character
#
@@ -191,8 +193,11 @@ func _on_dialog_line_typed(object, key):
text_node.visible_characters = -1
var time_to_disappear: float = _calculate_time_to_disappear()
if not $Timer.is_connected("timeout", self, "_on_dialog_finished"):
$Timer.connect("timeout", self, "_on_dialog_finished")
$Timer.start(time_to_disappear)
$Timer.connect("timeout", self, "_on_dialog_finished")
emit_signal("say_visible")
@@ -207,10 +212,11 @@ func _get_number_of_words() -> int:
# Ending the dialog
func _on_dialog_finished():
$Timer.stop()
# Only trigger to clear the text if we aren't limiting the clearing trigger to a click.
if not ESCProjectSettingsManager.get_setting(SimpleDialogPlugin.CLEAR_TEXT_BY_CLICK_ONLY):
if not ESCProjectSettingsManager.get_setting(SimpleDialogSettings.CLEAR_TEXT_BY_CLICK_ONLY):
emit_signal("say_finished")
queue_free()
# Handler managing pause notification from Escoria
@@ -223,7 +229,14 @@ func _on_paused():
# Handler managing resume notification from Escoria
func _on_resumed():
if not tween.is_active():
# 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
tween.resume_all()
func _on_tree_exited():
queue_free()

View File

@@ -46,7 +46,7 @@ onready var is_paused: bool = true
# Enable bbcode and catch the signal when a tween completed
func _ready():
_text_time_per_character = ProjectSettings.get_setting(
SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS
SimpleDialogSettings.TEXT_TIME_PER_LETTER_MS
)
if _text_time_per_character < 0:
@@ -54,15 +54,15 @@ func _ready():
self,
"%s setting must be a non-negative number. Will use default value of %s." %
[
SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS,
SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS_DEFAULT_VALUE
SimpleDialogSettings.TEXT_TIME_PER_LETTER_MS,
SimpleDialogSettings.TEXT_TIME_PER_LETTER_MS_DEFAULT_VALUE
]
)
_text_time_per_character = SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS_DEFAULT_VALUE
_text_time_per_character = SimpleDialogSettings.TEXT_TIME_PER_LETTER_MS_DEFAULT_VALUE
_fast_text_time_per_character = ProjectSettings.get_setting(
SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS_FAST
SimpleDialogSettings.TEXT_TIME_PER_LETTER_MS_FAST
)
if _fast_text_time_per_character < 0:
@@ -70,15 +70,15 @@ func _ready():
self,
"%s setting must be a non-negative number. Will use default value of %s." %
[
SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS_FAST,
SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS_FAST_DEFAULT_VALUE
SimpleDialogSettings.TEXT_TIME_PER_LETTER_MS_FAST,
SimpleDialogSettings.TEXT_TIME_PER_LETTER_MS_FAST_DEFAULT_VALUE
]
)
_fast_text_time_per_character = SimpleDialogPlugin.TEXT_TIME_PER_LETTER_MS_FAST_DEFAULT_VALUE
_fast_text_time_per_character = SimpleDialogSettings.TEXT_TIME_PER_LETTER_MS_FAST_DEFAULT_VALUE
_reading_speed_in_wpm = ProjectSettings.get_setting(
SimpleDialogPlugin.READING_SPEED_IN_WPM
SimpleDialogSettings.READING_SPEED_IN_WPM
)
if _reading_speed_in_wpm <= 0:
@@ -86,12 +86,12 @@ func _ready():
self,
"%s setting must be a positive number. Will use default value of %s." %
[
SimpleDialogPlugin.READING_SPEED_IN_WPM,
SimpleDialogPlugin.READING_SPEED_IN_WPM_DEFAULT_VALUE
SimpleDialogSettings.READING_SPEED_IN_WPM,
SimpleDialogSettings.READING_SPEED_IN_WPM_DEFAULT_VALUE
]
)
_reading_speed_in_wpm = SimpleDialogPlugin.READING_SPEED_IN_WPM_DEFAULT_VALUE
_reading_speed_in_wpm = SimpleDialogSettings.READING_SPEED_IN_WPM_DEFAULT_VALUE
_word_regex.compile("\\S+")
@@ -114,14 +114,9 @@ func _process(delta):
.get_global_transform_with_canvas().origin
rect_position.x -= rect_size.x / 2
if rect_position.x < 0:
rect_position.x = 0
_account_for_margin_x()
var screen_margin = rect_position.x + rect_size.x - \
ProjectSettings.get("display/window/size/width")
if screen_margin > 0:
rect_position.x -= screen_margin
_account_for_margin_y()
# Make a character say something
@@ -156,14 +151,9 @@ func say(character: String, line: String) :
rect_position.x = 0
rect_size.x = ProjectSettings.get_setting("display/window/size/width")
if rect_position.x < 0:
rect_position.x = 0
_account_for_margin_x()
var screen_margin = rect_position.x + rect_size.x - \
ProjectSettings.get("display/window/size/width")
if screen_margin > 0:
rect_position.x -= screen_margin
_account_for_margin_y()
_current_character.start_talking()
@@ -227,7 +217,7 @@ func _get_number_of_words() -> int:
# Ending the dialog
func _on_dialog_finished():
# Only trigger to clear the text if we aren't limiting the clearing trigger to a click.
if not ESCProjectSettingsManager.get_setting(SimpleDialogPlugin.CLEAR_TEXT_BY_CLICK_ONLY):
if not ESCProjectSettingsManager.get_setting(SimpleDialogSettings.CLEAR_TEXT_BY_CLICK_ONLY):
emit_signal("say_finished")
@@ -254,3 +244,25 @@ func _stop_character_talking():
# Make the speaking item animation stop talking, if it is still alive
if is_instance_valid(_current_character) and _current_character != null:
_current_character.stop_talking()
func _account_for_margin_x() -> void:
if rect_position.x < 0:
rect_position.x = 0
var screen_margin_x = rect_position.x + rect_size.x - \
ProjectSettings.get("display/window/size/width")
if screen_margin_x > 0:
rect_position.x -= screen_margin_x
func _account_for_margin_y() -> void:
if rect_position.y < 0:
rect_position.y = 0
var screen_margin_y = rect_position.y + rect_size.y - \
ProjectSettings.get("display/window/size/height")
if screen_margin_y > 0:
rect_position.y -= screen_margin_y