feat: Implemented all dialog features. Fixes #345 (#376)

Co-authored-by: Dennis Ploeger <develop@dieploegers.de>
Co-authored-by: dploeger <dploeger@users.noreply.github.com>
This commit is contained in:
Dennis Ploeger
2021-08-27 08:15:52 +02:00
committed by GitHub
parent 2ed184ad4a
commit 1f28fdc8f3
19 changed files with 418 additions and 172 deletions

View File

@@ -5,7 +5,7 @@ class_name ESCDialog
# Regex that matches dialog lines
const REGEX = \
'^(\\s*)\\?( (?<type>[^ ]+))?( (?<avatar>[^ ]+))?' +\
'^(\\s*)\\?( (?<avatar>[^ ]+))?' +\
'( (?<timeout>[^ ]+))?( (?<timeout_option>.+))?$'
@@ -14,11 +14,8 @@ const END_REGEX = \
'^(?<indent>\\s*)!.*$'
# Dialog type
var type: String = ""
# Avatar used in the dialog
var avatar: String = ""
var avatar: String = "-"
# Timeout until the timeout_option option is selected. Use 0 for no timeout
var timeout: int = 0
@@ -37,8 +34,6 @@ func _init(dialog_string: String):
if dialog_regex.search(dialog_string):
for result in dialog_regex.search_all(dialog_string):
if "type" in result.names:
self.type = escoria.utils.get_re_group(result, "type")
if "avatar" in result.names:
self.avatar = escoria.utils.get_re_group(result, "avatar")
if "timeout" in result.names:
@@ -58,8 +53,22 @@ func _init(dialog_string: String):
)
# Dialogs have no conditions, just return true
# Check if dialog is valid
func is_valid() -> bool:
if self.avatar != "-" and not ResourceLoader.exists(self.avatar):
escoria.logger.report_errors(
"Avatar scene not found: %s" % self.avatar,
[]
)
return false
if self.timeout_option > self.options.size() \
or self.timeout_option < 0:
escoria.logger.report_errors(
"Invalid timeout_option parameter given: %d" % self.timeout_option,
[]
)
return false
return true

View File

@@ -18,62 +18,41 @@ signal dialog_line_finished
# Wether the player is currently speaking
var is_speaking = false
# Reference to the dialog UI
var _dialog_ui = null
# Reference to the dialog chooser UI
var _dialog_chooser_ui = null
var _dialog_chooser_ui: ESCDialogOptionsChooser = null
# Register the dialog player and load the dialog resources
func _ready():
if !Engine.is_editor_hint():
escoria.dialog_player = self
preload_resources(ProjectSettings.get_setting("escoria/ui/dialogs_folder"))
_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
#
# #### Parameters
#
# - event: The input event
func _input(event):
if event is InputEventMouseButton and \
event.pressed:
finish_fast()
# Preload the dialog UI resources
#
# #### Parameters
#
# - path: Path where the actual dialog UI resources are located
func preload_resources(path: String) -> void:
var dialog_folder := Directory.new()
if !path.empty() and dialog_folder.open(path) == OK:
dialog_folder.list_dir_begin()
var file_name = dialog_folder.get_next()
while file_name != "":
if !dialog_folder.current_is_dir() \
and file_name.get_extension() == "tscn":
var extension = "." + file_name.get_extension()
var basename = file_name.replace(extension, "")
if !has_resource(basename):
var file_path = "%s/%s" % [
dialog_folder.get_current_dir(),
file_name
]
var dialog_scene = load(file_path)
if dialog_scene != null:
add_resource(basename, dialog_scene)
file_name = dialog_folder.get_next()
else:
escoria.logger.report_errors(
"dialog_player.gd:preload_resources()",
[
"An error occurred when trying to access the path: %s." % path
]
)
# A short one line dialog
#
# #### Parameters
@@ -99,20 +78,26 @@ func finish_fast() -> void:
# Display a list of choices
#
# #### Parameters
#
# - dialog: The dialog to start
func start_dialog_choices(dialog: ESCDialog):
if dialog.options.empty():
escoria.logger.report_errors(
"dialog_player.gd:start_dialog_choices()",
["Received answers array was empty."]
)
_dialog_chooser_ui = get_resource("text_dialog_choice").instance()
get_parent().add_child(_dialog_chooser_ui)
_dialog_chooser_ui.set_answers(dialog.options)
_dialog_chooser_ui.set_dialog(dialog)
_dialog_chooser_ui.show_chooser()
# Called when an option was chosen
# 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):
emit_signal("option_chosen", option)
_dialog_chooser_ui.hide()
_dialog_chooser_ui.hide_chooser()

View File

@@ -0,0 +1,34 @@
# Base class for all dialog options implementations
extends Node
class_name ESCDialogOptionsChooser
# An option was chosen
#
# ##### Parameters
#
# - option: The dialog option that was chosen
signal option_chosen(option)
# The dialog to show
var dialog: ESCDialog
# Set the dialog used for the chooser
#
# #### Parameters
#
# - new_dialog: Dialog to set
func set_dialog(new_dialog: ESCDialog) -> void:
self.dialog = new_dialog
# Show the dialog chooser UI
func show_chooser() -> void:
escoria.logger.error("Dialog chooser did not implement the show method.")
# Hide the dialog chooser UI
func hide_chooser() -> void:
escoria.logger.error("Dialog chooser did not implement the hide method.")

View File

@@ -26,14 +26,15 @@ func set_escoria_ui_settings():
if !ProjectSettings.has_setting("escoria/ui/tooltip_follows_mouse"):
ProjectSettings.set_setting("escoria/ui/tooltip_follows_mouse", true)
if !ProjectSettings.has_setting("escoria/ui/dialogs_folder"):
ProjectSettings.set_setting("escoria/ui/dialogs_folder", "")
var dialogs_folder_property_info = {
"name": "escoria/ui/dialogs_folder",
if !ProjectSettings.has_setting("escoria/ui/dialogs_chooser"):
ProjectSettings.set_setting("escoria/ui/dialogs_chooser", "")
var dialogs_chooser_property_info = {
"name": "escoria/ui/dialogs_chooser",
"type": TYPE_STRING,
"hint": PROPERTY_HINT_DIR
"hint": PROPERTY_HINT_FILE,
"hint_string": "*.tscn, *.scn"
}
ProjectSettings.add_property_info(dialogs_folder_property_info)
ProjectSettings.add_property_info(dialogs_chooser_property_info)
if !ProjectSettings.has_setting("escoria/ui/default_dialog_scene"):
ProjectSettings.set_setting("escoria/ui/default_dialog_scene", "")

View File

@@ -1,53 +1,67 @@
tool
extends Control
extends ESCDialogOptionsChooser
export(Color, RGB) var color_normal = Color(1.0,1.0,1.0,1.0)
export(Color, RGB) var color_hover = Color(165.0,42.0,42.0, 1.0)
export(Font) var font
var commands
func _ready():
for c in $ScrollContainer/VBoxContainer.get_children():
c.queue_free()
func _ready() -> void:
hide_chooser()
func _process(delta: float) -> void:
if $MarginContainer.visible and self.dialog and self.dialog.timeout > 0:
$TimerProgress.value = (
self.dialog.timeout - $Timer.time_left
) / self.dialog.timeout * 100
func set_answers(options : Array):
commands = options
for option in commands:
var new_answer_label = RichTextLabel.new()
new_answer_label.text = option.option
new_answer_label.fit_content_height = true
new_answer_label.add_font_override("normal_font", font)
$ScrollContainer/VBoxContainer.add_child(new_answer_label)
new_answer_label.fit_content_height = true
new_answer_label.connect("focus_entered", self, "_on_answer_focus_entered", [new_answer_label]) # Focus entered
new_answer_label.connect("focus_exited", self, "_on_answer_focus_exited", [new_answer_label]) # Focus exited
new_answer_label.connect("mouse_entered", self, "_on_answer_mouse_entered", [new_answer_label]) # Mouse entered
new_answer_label.connect("mouse_exited", self, "_on_answer_mouse_exited", [new_answer_label]) # Mouse exited
new_answer_label.connect("gui_input", self, "_on_answer_gui_input", [option]) # Clicks
func show_chooser():
var _vbox = $MarginContainer/ScrollContainer/VBoxContainer
for option_node in _vbox.get_children():
_vbox.remove_child(option_node)
_remove_avatar()
for option in self.dialog.options:
var _option_node = Button.new()
_option_node.text = (option as ESCDialogOption).option
_option_node.flat = true
_option_node.add_color_override("font_color", color_normal)
_option_node.add_color_override("font_color_hover", color_hover)
_option_node.add_font_override("font", font)
_vbox.add_child(_option_node)
_option_node.connect("pressed", self, "_on_answer_selected", [option])
if self.dialog.avatar != "-":
$AvatarContainer.add_child(
ResourceLoader.load(self.dialog.avatar).instance()
)
$MarginContainer.show()
if self.dialog.timeout > 0:
$Timer.start(self.dialog.timeout)
func _on_answer_gui_input(event : InputEvent, answer : ESCDialogOption):
if event is InputEventMouseButton and event.is_pressed():
escoria.dialog_player.play_dialog_option_chosen(answer)
func _on_answer_mouse_entered(answer_node : Node):
var text = answer_node.text
answer_node.clear()
answer_node.push_color(color_hover.to_html(false))
answer_node.append_bbcode(text)
answer_node.pop()
func hide_chooser():
$MarginContainer.hide()
func _on_answer_mouse_exited(answer_node : Node):
var text = answer_node.text
answer_node.clear()
answer_node.push_color(color_normal.to_html(false))
answer_node.append_bbcode(text)
answer_node.pop()
func _option_chosen(option: ESCDialogOption):
_remove_avatar()
$TimerProgress.value = 0
emit_signal("option_chosen", option)
func _on_answer_focus_entered(answer_node : Node):
pass
func _on_answer_focus_exited(answer_node : Node):
pass
func _on_answer_selected(option: ESCDialogOption):
_option_chosen(option)
func _on_Timer_timeout() -> void:
_option_chosen(self.dialog.options[self.dialog.timeout_option - 1])
func _remove_avatar():
if $AvatarContainer.get_child_count() > 0:
$AvatarContainer.remove_child($AvatarContainer.get_child(0))

View File

@@ -1,33 +1,44 @@
[gd_scene load_steps=3 format=2]
[gd_scene load_steps=5 format=2]
[ext_resource path="res://addons/escoria-core/template_scenes/dialog_scenes/dialog_choosers/text_dialog_choice.gd" type="Script" id=1]
[ext_resource path="res://addons/escoria-core/game/assets/fonts/efmi/efmi_font.tres" type="DynamicFont" id=2]
[node name="text_dialog_choice" type="MarginContainer"]
anchor_right = 1.0
anchor_bottom = 1.0
custom_constants/margin_top = 20
custom_constants/margin_left = 20
[sub_resource type="Gradient" id=1]
colors = PoolColorArray( 1, 0, 0, 1, 1, 0, 0, 1 )
[sub_resource type="GradientTexture" id=2]
gradient = SubResource( 1 )
[node name="text_dialog_choice" type="Node"]
script = ExtResource( 1 )
__meta__ = {
"_edit_use_anchors_": false
}
color_hover = Color( 0.647059, 0.164706, 0.164706, 1 )
font = ExtResource( 2 )
[node name="ScrollContainer" type="ScrollContainer" parent="."]
[node name="MarginContainer" type="MarginContainer" parent="."]
margin_left = 20.0
margin_top = 20.0
margin_right = 1280.0
margin_bottom = 800.0
margin_bottom = 900.0
mouse_filter = 2
custom_constants/margin_top = 20
custom_constants/margin_left = 20
__meta__ = {
"_edit_use_anchors_": false
}
[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer"]
margin_left = 20.0
margin_top = 20.0
margin_right = 1260.0
margin_bottom = 880.0
scroll_horizontal_enabled = false
__meta__ = {
"_edit_use_anchors_": false
}
[node name="VBoxContainer" type="VBoxContainer" parent="ScrollContainer"]
margin_right = 1260.0
margin_bottom = 780.0
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/ScrollContainer"]
margin_right = 1240.0
margin_bottom = 860.0
size_flags_horizontal = 3
size_flags_vertical = 3
custom_constants/separation = 20
@@ -35,9 +46,19 @@ __meta__ = {
"_edit_use_anchors_": false
}
[node name="example" type="RichTextLabel" parent="ScrollContainer/VBoxContainer"]
margin_right = 1260.0
margin_bottom = 25.0
custom_fonts/normal_font = ExtResource( 2 )
text = "This answer is an example for vizualisation purpose. You can safely keep it in the scene as it will be removed on game launch."
fit_content_height = true
[node name="Timer" type="Timer" parent="."]
one_shot = true
[node name="TimerProgress" type="TextureProgress" parent="."]
anchor_right = 1.0
rect_min_size = Vector2( 0, 20 )
texture_progress = SubResource( 2 )
nine_patch_stretch = true
__meta__ = {
"_edit_use_anchors_": false
}
[node name="AvatarContainer" type="Node2D" parent="."]
position = Vector2( 29.1458, 120.012 )
[connection signal="timeout" from="Timer" to="." method="_on_Timer_timeout"]