2024-08-16 00:24:27 +02:00
|
|
|
class_name DialogueBaloon
|
2024-07-12 14:32:33 +02:00
|
|
|
extends CanvasLayer
|
|
|
|
|
|
|
|
## The action to use for advancing the dialogue
|
|
|
|
@export var next_action: StringName = &"ui_accept"
|
|
|
|
|
|
|
|
## The action to use to skip typing the dialogue
|
|
|
|
@export var skip_action: StringName = &"ui_cancel"
|
|
|
|
|
|
|
|
@onready var balloon: Control = %Balloon
|
|
|
|
@onready var character_label: RichTextLabel = %CharacterLabel
|
|
|
|
@onready var dialogue_label: DialogueLabel = %DialogueLabel
|
|
|
|
@onready var responses_menu: DialogueResponsesMenu = %ResponsesMenu
|
|
|
|
|
|
|
|
## The dialogue resource
|
|
|
|
var resource: DialogueResource
|
|
|
|
|
|
|
|
## Temporary game states
|
|
|
|
var temporary_game_states: Array = []
|
|
|
|
|
|
|
|
## See if we are waiting for the player
|
|
|
|
var is_waiting_for_input: bool = false
|
|
|
|
|
|
|
|
## See if we are running a long mutation and should hide the balloon
|
|
|
|
var will_hide_balloon: bool = false
|
|
|
|
|
|
|
|
## The current line
|
|
|
|
var dialogue_line: DialogueLine:
|
|
|
|
set(next_dialogue_line):
|
|
|
|
is_waiting_for_input = false
|
|
|
|
balloon.focus_mode = Control.FOCUS_ALL
|
|
|
|
balloon.grab_focus()
|
|
|
|
|
|
|
|
# The dialogue has finished so close the balloon
|
|
|
|
if not next_dialogue_line:
|
|
|
|
queue_free()
|
|
|
|
return
|
|
|
|
|
|
|
|
# If the node isn't ready yet then none of the labels will be ready yet either
|
|
|
|
if not is_node_ready():
|
|
|
|
await ready
|
|
|
|
|
|
|
|
dialogue_line = next_dialogue_line
|
|
|
|
|
|
|
|
character_label.visible = not dialogue_line.character.is_empty()
|
|
|
|
character_label.text = tr(dialogue_line.character, "dialogue")
|
|
|
|
|
|
|
|
dialogue_label.hide()
|
|
|
|
dialogue_label.dialogue_line = dialogue_line
|
|
|
|
|
|
|
|
responses_menu.hide()
|
|
|
|
responses_menu.set_responses(dialogue_line.responses)
|
|
|
|
|
|
|
|
# Show our balloon
|
|
|
|
balloon.show()
|
|
|
|
will_hide_balloon = false
|
|
|
|
|
|
|
|
dialogue_label.show()
|
|
|
|
if not dialogue_line.text.is_empty():
|
|
|
|
dialogue_label.type_out()
|
|
|
|
await dialogue_label.finished_typing
|
|
|
|
|
|
|
|
# Wait for input
|
|
|
|
if dialogue_line.responses.size() > 0:
|
|
|
|
balloon.focus_mode = Control.FOCUS_NONE
|
|
|
|
responses_menu.show()
|
|
|
|
elif dialogue_line.time != "":
|
|
|
|
var time = dialogue_line.text.length() * 0.02 if dialogue_line.time == "auto" else dialogue_line.time.to_float()
|
|
|
|
await get_tree().create_timer(time).timeout
|
|
|
|
next(dialogue_line.next_id)
|
|
|
|
else:
|
|
|
|
is_waiting_for_input = true
|
|
|
|
balloon.focus_mode = Control.FOCUS_ALL
|
|
|
|
balloon.grab_focus()
|
|
|
|
get:
|
|
|
|
return dialogue_line
|
|
|
|
|
|
|
|
|
|
|
|
func _ready() -> void:
|
|
|
|
balloon.hide()
|
|
|
|
Engine.get_singleton("DialogueManager").mutated.connect(_on_mutated)
|
|
|
|
|
|
|
|
# If the responses menu doesn't have a next action set, use this one
|
|
|
|
if responses_menu.next_action.is_empty():
|
|
|
|
responses_menu.next_action = next_action
|
|
|
|
|
|
|
|
|
|
|
|
func _unhandled_input(_event: InputEvent) -> void:
|
|
|
|
# Only the balloon is allowed to handle input while it's showing
|
|
|
|
get_viewport().set_input_as_handled()
|
|
|
|
|
|
|
|
|
|
|
|
func _notification(what: int) -> void:
|
|
|
|
# Detect a change of locale and update the current dialogue line to show the new language
|
|
|
|
if what == NOTIFICATION_TRANSLATION_CHANGED and is_instance_valid(dialogue_label):
|
|
|
|
var visible_ratio = dialogue_label.visible_ratio
|
|
|
|
self.dialogue_line = await resource.get_next_dialogue_line(dialogue_line.id)
|
|
|
|
if visible_ratio < 1:
|
|
|
|
dialogue_label.skip_typing()
|
|
|
|
|
|
|
|
|
|
|
|
## Start some dialogue
|
|
|
|
func start(dialogue_resource: DialogueResource, title: String, extra_game_states: Array = []) -> void:
|
|
|
|
temporary_game_states = [self] + extra_game_states
|
|
|
|
is_waiting_for_input = false
|
|
|
|
resource = dialogue_resource
|
2024-11-15 13:09:38 +01:00
|
|
|
InteractionSystem.start_conversation(dialogue_resource)
|
2024-07-12 14:32:33 +02:00
|
|
|
self.dialogue_line = await resource.get_next_dialogue_line(title, temporary_game_states)
|
|
|
|
|
|
|
|
|
|
|
|
## Go to the next line
|
|
|
|
func next(next_id: String) -> void:
|
|
|
|
self.dialogue_line = await resource.get_next_dialogue_line(next_id, temporary_game_states)
|
|
|
|
|
|
|
|
|
|
|
|
#region Signals
|
|
|
|
|
|
|
|
|
|
|
|
func _on_mutated(_mutation: Dictionary) -> void:
|
|
|
|
is_waiting_for_input = false
|
|
|
|
will_hide_balloon = true
|
|
|
|
get_tree().create_timer(0.1).timeout.connect(func():
|
|
|
|
if will_hide_balloon:
|
|
|
|
will_hide_balloon = false
|
|
|
|
balloon.hide()
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
func _on_balloon_gui_input(event: InputEvent) -> void:
|
|
|
|
# See if we need to skip typing of the dialogue
|
|
|
|
if dialogue_label.is_typing:
|
|
|
|
var mouse_was_clicked: bool = event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.is_pressed()
|
|
|
|
var skip_button_was_pressed: bool = event.is_action_pressed(skip_action)
|
|
|
|
if mouse_was_clicked or skip_button_was_pressed:
|
|
|
|
get_viewport().set_input_as_handled()
|
|
|
|
dialogue_label.skip_typing()
|
|
|
|
return
|
|
|
|
|
|
|
|
if not is_waiting_for_input: return
|
|
|
|
if dialogue_line.responses.size() > 0: return
|
|
|
|
|
|
|
|
# When there are no response options the balloon itself is the clickable thing
|
|
|
|
get_viewport().set_input_as_handled()
|
|
|
|
|
|
|
|
if event is InputEventMouseButton and event.is_pressed() and event.button_index == MOUSE_BUTTON_LEFT:
|
|
|
|
next(dialogue_line.next_id)
|
|
|
|
elif event.is_action_pressed(next_action) and get_viewport().gui_get_focus_owner() == balloon:
|
|
|
|
next(dialogue_line.next_id)
|
|
|
|
|
|
|
|
|
|
|
|
func _on_responses_menu_response_selected(response: DialogueResponse) -> void:
|
|
|
|
next(response.next_id)
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|