godot-vn/ui/dialog_box.gd

423 lines
11 KiB
GDScript

extends Control
# load from dialog vm
const Dialog = preload("res://dialog_vm.gd")
const DialogController = preload("res://dialog_controller.gd")
# classes from there
const DialogSegment = Dialog.DialogSegment
const DialogWithSubjectSegment = Dialog.DialogWithSubjectSegment
const DialogWithSubjectExpressionSegment = Dialog.DialogWithSubjectExpressionSegment
const QuestionSegment = Dialog.QuestionSegment
const ChoiceSegment = Dialog.ChoiceSegment
const ChoiceWithJump = Dialog.ChoiceWithJump
const Choice = Dialog.Choice
const WaitSegment = Dialog.YieldCallSegment
# resources specific to this dialogy thingy
const DialogChoices = preload("res://ui/dialog_choices.tscn")
const DialogChoice = preload("res://ui/dialog_choice.tscn")
var IS_DEBUG = false
var current_dialog
var current_dialog_controller
# ui elements
onready var advance_button = get_node("top_panel/auto_btn")
onready var fastforward_button = get_node("top_panel/ffwd_btn")
onready var title_label = get_node("body_container/bottom_panel/subject_margin/subject_panel/subject_label")
onready var body_container = get_node("body_container")
onready var body_label = get_node("body_container/bottom_panel/body_margin/body_label")
# clickable regions, for accessibility
onready var top_panel = get_node("top_panel")
onready var audio_player = get_node("audio_player")
# params to set from outside
var dialog_path
# activeness
var active
# tween to fade in/out
var tween
# timer
var timer
# anim player, if any
var anim_player
# linkin in ur get_node relative to where you want
var get_node_fnc
# various main dialog related constants
# signals
signal entered_dialog
signal exited_frame
# character related signals
signal on_speaker_expression_changed(spkr, expr)
signal on_dialog_init(d)
signal on_dialog_completed(d)
# setters
func set_auto_advance(v):
current_dialog_controller.set_advance_state(v)
advance_button.pressed = v
# signals from dialog controller
func _on_printing_changed(v):
pass
# next_arrow.visible = v
func _on_reading_speed_changed(new_value):
current_dialog_controller.set_reading_speed(new_value)
func set_advance_state():
current_dialog_controller.set_advance_state(advance_button.is_pressed())
func set_fastforward_state():
var ffwd_pressed = fastforward_button.is_pressed()
current_dialog_controller.set_fastforward_state(fastforward_button.is_pressed())
advance_button.set_disabled(ffwd_pressed)
# init functions, must be called before use
func set_dialog_path(path):
dialog_path = path
func set_dialog_vm(vm):
current_dialog = vm
func set_dialog_controller(c):
current_dialog_controller = c
func set_dialog_anim_player(p):
anim_player = p
func set_get_character_function(fnc):
get_node_fnc = fnc
# initially invisible
func _enter_tree():
modulate.a = 0
func _ready():
advance_button.connect("pressed", self, "set_advance_state")
fastforward_button.connect("pressed", self, "set_fastforward_state")
# clicking to advance
body_container.connect("gui_input", self, "_on_body_click")
top_panel.connect("gui_input", self, "_on_body_click")
Game.connect("on_reading_speed_changed", self, "_on_reading_speed_changed")
Game.connect("on_settings_enter", self, "_on_settings_enter")
Game.connect("on_settings_exit", self, "_on_settings_exit")
tween = Tween.new()
add_child(tween)
timer = Timer.new()
add_child(timer)
func _on_body_click(ev):
current_dialog_controller._on_body_click(is_active(), ev)
func _register_func(r, reg_name, func_name):
if r.has(reg_name):
r[reg_name].set_instance(self)
else:
r[reg_name] = funcref(self, func_name)
func _register_functions():
var r = current_dialog.get_local_registry()
# normal functions
_register_func(r, "SET_EXPR", "_set_expr_fnc")
_register_func(r, "FLIP", "_flip_char")
_register_func(r, "HIDE", "_hide_char")
_register_func(r, "SHOW", "_show_char")
# yieldy function
_register_func(r, "WAIT", "_wait_dialog_fnc")
# anim shite, also yieldy
_register_func(r, "PLAY_ANIM", "_play_anim_fnc")
func _play_anim_fnc(args):
if args.size() == 1:
var anim_name = args[0]
if anim_player:
anim_player.play(anim_name)
return [anim_player, "animation_finished"]
func _wait_dialog_fnc(args):
if args.size() == 1:
var time = float(args[0])
print("Dialog: waiting for: %f seconds" % time)
timer.wait_time = time
timer.one_shot = true
timer.start()
return [timer, "timeout"]
func _set_expr_fnc(args):
if args.size() == 2:
var speaker = args[0]
var expr = args[1]
_set_speaker_expression(speaker, expr)
func _show_char(args):
var char_name = args[0]
var character = get_node_fnc.call(char_name)
_do_show_char(character)
func _do_show_char(c):
tween.interpolate_property(
c, "modulate:a", c.modulate.a, 1.0,
1.0, Tween.TRANS_QUAD,
Tween.EASE_IN_OUT
)
tween.start()
func _hide_char(args):
var char_name = args[0]
var character = get_node_fnc.call(char_name)
_do_hide_char(character)
func _do_hide_char(c):
tween.interpolate_property(
c, "modulate:a", c.modulate.a, 0.0,
1.0, Tween.TRANS_QUAD,
Tween.EASE_IN_OUT
)
tween.start()
func _flip_char(args):
var char_name = args[0]
var character = get_node_fnc.call(char_name)
_do_flip_char(character)
func _do_flip_char(c):
c.flip_h = !c.flip_h
func _on_settings_enter(s):
if is_active():
visible = false
func _on_settings_exit(s):
if is_active():
visible = true
func reload_dialog():
initialize_dialog()
func get_local_registry():
return current_dialog.get_local_registry()
func initialize_dialog():
assert(dialog_path != null)
var dialog_file = File.new()
dialog_file.open(dialog_path, File.READ)
var text = dialog_file.get_as_text()
dialog_file.close()
var dialog = Dialog.dialog_from_text(text)
current_dialog = dialog
# also setup kv store for dialog variables
current_dialog.set_registry(Game.current_registry)
# set up controller too yes
current_dialog_controller = DialogController.new(dialog, audio_player)
# register functions
_register_functions()
# signals
current_dialog_controller.connect("on_update_dialog", self, "_on_update_dialog")
current_dialog_controller.connect("on_dialog_completed", self, "_on_dialog_completed")
current_dialog_controller.connect("on_printing_changed", self, "_on_printing_changed")
emit_signal("on_dialog_init", self)
func _on_dialog_completed(d):
emit_signal("on_dialog_completed", self)
func _on_update_dialog(s):
_update_dialog(s)
func _fade_in():
var FADE_TIME = 1.0
tween.interpolate_property(
self,
"modulate:a", 0.0, 1.0, FADE_TIME,
Tween.TRANS_QUAD, Tween.EASE_IN
)
tween.start()
func _fade_out():
var FADE_TIME = 1.0
tween.interpolate_property(
self,
"modulate:a", 1.0, 0.0, FADE_TIME,
Tween.TRANS_QUAD, Tween.EASE_OUT
)
tween.start()
yield(tween, "tween_completed")
visible = false
func enter_frame():
_fade_in()
var r = Game.current_registry
r["IS_IN_DIALOG"] = true
# reload if is debug
if IS_DEBUG: reload_dialog()
active = true
visible = true
current_dialog.reset()
# next_arrow.visible = false
emit_signal("entered_dialog")
# dialogs
var first_segment = current_dialog.advance()
_update_dialog(first_segment)
func is_active():
return active
func exit_frame():
_fade_out()
# HACK: cludge to reset state
advance_button.set_pressed(false)
fastforward_button.set_pressed(false)
advance_button.emit_signal("pressed")
fastforward_button.emit_signal("pressed")
current_dialog_controller.reset()
active = false
var r = Game.current_registry
r["IS_IN_DIALOG"] = false
emit_signal("exited_frame")
func _set_speaker_subject(subject):
if Game.Characters.has(subject):
var data = Game.Characters[subject]
var subject_name = data.name
var subject_colour = data.name_colour
var subject_voice = data.voice
current_dialog_controller.chars_per_second = Game.DEFAULT_CHARS_PER_SECOND * data.talking_speed
_set_speaker_name(subject_name, subject_colour)
if subject_voice != null:
_set_speaker_voice(subject_voice)
else:
var subject_name = subject
current_dialog_controller.chars_per_second = Game.DEFAULT_CHARS_PER_SECOND
_set_speaker_name(subject_name, Color(1, 1, 1))
func _get_speaker_name():
return title_label.text
func _set_speaker_name(speaker_name, colour = Color(1, 1, 1)):
title_label.set_text(speaker_name)
title_label.modulate = colour
func _set_speaker_voice(voice_sample):
audio_player.stream = voice_sample
func _set_speaker_expression(speaker, expression):
emit_signal("on_speaker_expression_changed", speaker, expression)
func _choice_made(choice_modal, choice):
if IS_DEBUG: print("chose: ", choice.get_choice())
# Game.add_history_entry("you", choice.get_choice())
remove_child(choice_modal)
if choice is ChoiceWithJump:
# HACK: fix this later or make this unnecessary
var current_segment = current_dialog.jump(choice.target)
call_deferred("_update_dialog", current_segment)
else:
_update_dialog(choice)
current_dialog_controller.waiting_for_answer = false
# loop de loop
func _update_dialog(segment):
if segment is ChoiceSegment:
var choices = segment.get_choices()
# create new dialog choices and shit
var new_choices = DialogChoices.instance()
var vbox = new_choices.get_node("vbox")
for choice in choices:
var new_choice = DialogChoice.instance()
new_choice.text = choice.get_choice()
new_choice.connect("pressed", self, "_choice_made", [new_choices, choice])
vbox.add_child(new_choice)
add_child(new_choices)
elif segment is Choice:
var new_content = segment.get_response()
if typeof(new_content) == TYPE_OBJECT and (new_content is DialogWithSubjectSegment or new_content is DialogWithSubjectExpressionSegment):
_update_dialog(new_content)
else:
body_label.text = new_content
# Game.add_history_entry(_get_speaker_name(), new_content)
elif segment is DialogSegment or segment is DialogWithSubjectSegment or segment is DialogWithSubjectExpressionSegment:
var new_content = segment.get_content()
body_label.visible_characters = 0
body_label.text = new_content
if segment is DialogWithSubjectSegment:
_set_speaker_subject(segment.subject)
# Game.add_history_entry(_get_speaker_name(), new_content)
elif segment is DialogWithSubjectExpressionSegment:
_set_speaker_subject(segment.subject)
_set_speaker_expression(segment.subject, segment.expression)
# Game.add_history_entry("%s (%s)" % [_get_speaker_name(), segment.expression], new_content)
else:
pass
# Game.add_history_entry(_get_speaker_name(), new_content)
elif segment is WaitSegment:
var new_content = segment.get_content()
body_label.visible_characters = 0
body_label.text = new_content
# whatever subject you want during functions to wait on, animations and such
_set_speaker_subject("")
else:
print("encountered unknown segment: " + segment.get_content())
current_dialog_controller.update_logic_dialog(segment)
func _process(delta):
if is_active():
current_dialog_controller.tick(delta, body_label)