162 lines
5.6 KiB
GDScript
162 lines
5.6 KiB
GDScript
extends Control
|
|
|
|
signal dialogue_choice_selected(index: int, text: String)
|
|
|
|
const CHOICE_NORMAL := preload("res://assets/ccs_cases_2/images/doctor/dialogue-choice-normal.png")
|
|
const CHOICE_HOVER := preload("res://assets/ccs_cases_2/images/doctor/dialogue-choice-hover.png")
|
|
const CHOICE_PRESSED := preload("res://assets/ccs_cases_2/images/doctor/dialogue-choice-pressed.png")
|
|
const CHOICE_CROP := Rect2(80.0, 200.0, 1610.0, 485.0)
|
|
|
|
@onready var hour_hand: Node2D = $ClockMount/HourHand
|
|
@onready var minute_hand: Node2D = $ClockMount/MinuteHand
|
|
@onready var second_hand: Node2D = $ClockMount/SecondHand
|
|
@onready var patient: TextureRect = $Patient
|
|
@onready var dialogue_bubble: Control = $DialogueBubble
|
|
@onready var dialogue_text: Label = $DialogueBubble/Text
|
|
@onready var choice_container: VBoxContainer = $DialogueChoices
|
|
|
|
var blink_tween: Tween
|
|
var dialogue_tween: Tween
|
|
|
|
|
|
func _ready() -> void:
|
|
_update_clock()
|
|
_schedule_patient_blink()
|
|
show_patient_dialogue("Hello, doctor.")
|
|
show_dialogue_choices([
|
|
"What brings you in today?",
|
|
"How are you feeling today?",
|
|
])
|
|
|
|
|
|
func _process(_delta: float) -> void:
|
|
_update_clock()
|
|
|
|
|
|
func _update_clock() -> void:
|
|
var time := Time.get_time_dict_from_system()
|
|
var unix_time := Time.get_unix_time_from_system()
|
|
var second := float(time.second) + fmod(unix_time, 1.0)
|
|
var minute := float(time.minute) + second / 60.0
|
|
var hour := float(time.hour % 12) + minute / 60.0
|
|
hour_hand.rotation = TAU * hour / 12.0
|
|
minute_hand.rotation = TAU * minute / 60.0
|
|
second_hand.rotation = TAU * second / 60.0
|
|
|
|
|
|
func _schedule_patient_blink() -> void:
|
|
blink_tween = create_tween()
|
|
blink_tween.tween_interval(randf_range(2.8, 5.2))
|
|
blink_tween.tween_method(_set_patient_blink, 0.0, 1.0, 0.075).set_trans(
|
|
Tween.TRANS_SINE
|
|
).set_ease(Tween.EASE_IN)
|
|
blink_tween.tween_interval(0.045)
|
|
blink_tween.tween_method(_set_patient_blink, 1.0, 0.0, 0.10).set_trans(
|
|
Tween.TRANS_SINE
|
|
).set_ease(Tween.EASE_OUT)
|
|
blink_tween.tween_callback(_schedule_patient_blink)
|
|
|
|
|
|
func _set_patient_blink(amount: float) -> void:
|
|
patient.material.set_shader_parameter("blink_amount", amount)
|
|
|
|
|
|
func show_patient_dialogue(text: String, characters_per_second: float = 35.0) -> void:
|
|
if dialogue_tween and dialogue_tween.is_valid():
|
|
dialogue_tween.kill()
|
|
dialogue_text.text = text
|
|
dialogue_text.visible_ratio = 0.0
|
|
dialogue_bubble.visible = true
|
|
_resize_dialogue_bubble()
|
|
var reveal_duration := float(text.length()) / maxf(characters_per_second, 1.0)
|
|
dialogue_tween = create_tween()
|
|
dialogue_tween.tween_property(dialogue_text, "visible_ratio", 1.0, reveal_duration)
|
|
|
|
|
|
func hide_patient_dialogue() -> void:
|
|
if dialogue_tween and dialogue_tween.is_valid():
|
|
dialogue_tween.kill()
|
|
dialogue_bubble.visible = false
|
|
|
|
|
|
func is_patient_dialogue_visible() -> bool:
|
|
return dialogue_bubble.visible
|
|
|
|
|
|
func _resize_dialogue_bubble() -> void:
|
|
# Label wrapping is calculated from the fixed text width immediately after
|
|
# assignment, so explicit newlines and automatic wraps both affect height.
|
|
var line_count := maxi(dialogue_text.get_line_count(), 1)
|
|
var target_height := clampf(110.0 + line_count * 30.0, 140.0, 320.0)
|
|
dialogue_bubble.size.y = target_height
|
|
|
|
|
|
func show_dialogue_choices(options: Array[String]) -> void:
|
|
hide_dialogue_choices()
|
|
var stack_height := options.size() * 97.0 + 8.0
|
|
choice_container.offset_top = -4.0 - stack_height
|
|
for index in options.size():
|
|
var option_text := options[index]
|
|
var button := TextureButton.new()
|
|
button.custom_minimum_size = Vector2(420.0, 105.0)
|
|
button.ignore_texture_size = true
|
|
button.stretch_mode = TextureButton.STRETCH_SCALE
|
|
button.texture_normal = _crop_choice_texture(CHOICE_NORMAL)
|
|
button.texture_hover = _crop_choice_texture(CHOICE_HOVER)
|
|
button.texture_pressed = _crop_choice_texture(CHOICE_PRESSED)
|
|
button.self_modulate = Color(1.0, 1.0, 1.0, 0.86)
|
|
button.focus_mode = Control.FOCUS_ALL
|
|
button.mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
|
|
button.mouse_entered.connect(_on_choice_hover.bind(button, true))
|
|
button.mouse_exited.connect(_on_choice_hover.bind(button, false))
|
|
button.button_down.connect(_on_choice_pressed_visual.bind(button, true))
|
|
button.button_up.connect(_on_choice_pressed_visual.bind(button, false))
|
|
button.pressed.connect(_on_dialogue_choice_pressed.bind(index, option_text))
|
|
|
|
var label := Label.new()
|
|
label.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
|
label.offset_left = 48.0
|
|
label.offset_top = 14.0
|
|
label.offset_right = -48.0
|
|
label.offset_bottom = -14.0
|
|
label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
label.add_theme_color_override("font_color", Color(0.055, 0.105, 0.145))
|
|
label.add_theme_font_size_override("font_size", 19)
|
|
label.text = option_text
|
|
label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
|
label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
|
button.add_child(label)
|
|
choice_container.add_child(button)
|
|
choice_container.visible = not options.is_empty()
|
|
|
|
|
|
func hide_dialogue_choices() -> void:
|
|
for child in choice_container.get_children():
|
|
child.queue_free()
|
|
choice_container.visible = false
|
|
|
|
|
|
func _crop_choice_texture(source: Texture2D) -> AtlasTexture:
|
|
var cropped := AtlasTexture.new()
|
|
cropped.atlas = source
|
|
cropped.region = CHOICE_CROP
|
|
cropped.filter_clip = true
|
|
return cropped
|
|
|
|
|
|
func _on_choice_hover(button: TextureButton, hovered: bool) -> void:
|
|
button.self_modulate = (
|
|
Color(1.08, 1.05, 0.96, 0.94)
|
|
if hovered
|
|
else Color(1.0, 1.0, 1.0, 0.86)
|
|
)
|
|
|
|
|
|
func _on_choice_pressed_visual(button: TextureButton, pressed: bool) -> void:
|
|
button.position.y += 2.0 if pressed else -2.0
|
|
|
|
|
|
func _on_dialogue_choice_pressed(index: int, option_text: String) -> void:
|
|
dialogue_choice_selected.emit(index, option_text)
|
|
hide_dialogue_choices()
|