Refine graphical CCS interview flow

This commit is contained in:
hz
2026-08-06 04:58:15 -05:00
parent ee8e8cf787
commit 2c7c5dd387
5 changed files with 141 additions and 87 deletions
+93 -23
View File
@@ -10,7 +10,8 @@ const CHOICE_HOVER := preload("res://assets/ccs_cases_2/images/doctor/dialogue-c
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)
const CHOICES_PER_PAGE := 4
const RESPONSE_HOLD_SECONDS := 1.25
const DIALOGUE_PAGE_MIN_CHARS := 32
const DIALOGUE_PAGE_MAX_CHARS := 82
@onready var hour_hand: Node2D = $ClockMount/HourHand
@onready var minute_hand: Node2D = $ClockMount/MinuteHand
@@ -18,6 +19,7 @@ const RESPONSE_HOLD_SECONDS := 1.25
@onready var patient: TextureRect = $Patient
@onready var dialogue_bubble: Control = $DialogueBubble
@onready var dialogue_text: Label = $DialogueBubble/Text
@onready var dialogue_continue_hint: Label = $DialogueBubble/ContinueHint
@onready var choice_area: Control = $DialogueChoiceArea
@onready var choice_container: HBoxContainer = $DialogueChoiceArea/DialogueChoices
@onready var choice_pager: HBoxContainer = $DialogueChoiceArea/ChoicePager
@@ -27,16 +29,20 @@ const RESPONSE_HOLD_SECONDS := 1.25
var blink_tween: Tween
var dialogue_tween: Tween
var response_hold_tween: Tween
var clock_seconds := 9.0 * 60.0 * 60.0
var debrief_panel: PanelContainer
var experience_mode := "management"
var pending_choices: Array = []
var available_choices: Array = []
var choice_page := 0
var dialogue_pages: Array[String] = []
var dialogue_page_index := 0
var dialogue_characters_per_second := 35.0
var dialogue_reveal_complete := false
func _ready() -> void:
dialogue_bubble.gui_input.connect(_on_dialogue_bubble_input)
previous_page_button.pressed.connect(_change_choice_page.bind(-1))
next_page_button.pressed.connect(_change_choice_page.bind(1))
_update_clock()
@@ -95,13 +101,25 @@ func _set_patient_blink(amount: float) -> void:
func show_patient_dialogue(text: String, characters_per_second: float = 35.0) -> void:
if dialogue_tween and dialogue_tween.is_valid():
dialogue_tween.kill()
if response_hold_tween and response_hold_tween.is_valid():
response_hold_tween.kill()
dialogue_text.text = text
dialogue_pages = _split_dialogue_pages(text)
dialogue_page_index = 0
dialogue_characters_per_second = characters_per_second
_show_dialogue_page()
func _show_dialogue_page() -> void:
if dialogue_tween and dialogue_tween.is_valid():
dialogue_tween.kill()
if dialogue_pages.is_empty():
complete_response_transition()
return
dialogue_text.text = dialogue_pages[dialogue_page_index]
dialogue_text.visible_ratio = 0.0
dialogue_reveal_complete = false
dialogue_continue_hint.visible = false
dialogue_bubble.visible = true
call_deferred("_resize_dialogue_bubble")
var reveal_duration := float(text.length()) / maxf(characters_per_second, 1.0)
dialogue_bubble.grab_focus()
var reveal_duration := float(dialogue_text.text.length()) / maxf(dialogue_characters_per_second, 1.0)
dialogue_tween = create_tween()
dialogue_tween.tween_property(dialogue_text, "visible_ratio", 1.0, reveal_duration)
dialogue_tween.finished.connect(_on_dialogue_reveal_finished)
@@ -110,9 +128,11 @@ func show_patient_dialogue(text: String, characters_per_second: float = 35.0) ->
func hide_patient_dialogue() -> void:
if dialogue_tween and dialogue_tween.is_valid():
dialogue_tween.kill()
if response_hold_tween and response_hold_tween.is_valid():
response_hold_tween.kill()
dialogue_bubble.visible = false
dialogue_continue_hint.visible = false
dialogue_pages = []
dialogue_page_index = 0
dialogue_reveal_complete = false
pending_choices = []
@@ -120,12 +140,6 @@ func is_patient_dialogue_visible() -> bool:
return dialogue_bubble.visible
func _resize_dialogue_bubble() -> void:
var line_count := maxi(dialogue_text.get_line_count(), 1)
var target_height := clampf(82.0 + line_count * 29.0, 150.0, 310.0)
dialogue_bubble.size.y = target_height
func configure_interview(node: Dictionary) -> void:
if debrief_panel != null and is_instance_valid(debrief_panel):
debrief_panel.queue_free()
@@ -148,6 +162,8 @@ func configure_interview(node: Dictionary) -> void:
func complete_dialogue_reveal() -> void:
if dialogue_reveal_complete:
return
if dialogue_tween and dialogue_tween.is_valid():
dialogue_tween.kill()
dialogue_text.visible_ratio = 1.0
@@ -156,23 +172,77 @@ func complete_dialogue_reveal() -> void:
func _on_dialogue_reveal_finished() -> void:
dialogue_tween = null
if response_hold_tween and response_hold_tween.is_valid():
response_hold_tween.kill()
response_hold_tween = create_tween()
response_hold_tween.tween_interval(RESPONSE_HOLD_SECONDS)
response_hold_tween.tween_callback(complete_response_transition)
dialogue_reveal_complete = true
dialogue_continue_hint.text = (
"Click to see questions ▼"
if dialogue_page_index >= dialogue_pages.size() - 1
else "Click to continue ▼"
)
dialogue_continue_hint.visible = true
func advance_dialogue() -> void:
if not dialogue_bubble.visible:
return
if not dialogue_reveal_complete:
complete_dialogue_reveal()
return
if dialogue_page_index < dialogue_pages.size() - 1:
dialogue_page_index += 1
_show_dialogue_page()
return
complete_response_transition()
func complete_response_transition() -> void:
if response_hold_tween and response_hold_tween.is_valid():
response_hold_tween.kill()
response_hold_tween = null
var choices := pending_choices
pending_choices = []
dialogue_bubble.visible = false
dialogue_continue_hint.visible = false
dialogue_pages = []
dialogue_page_index = 0
dialogue_reveal_complete = false
show_dialogue_choices(choices)
func _on_dialogue_bubble_input(event: InputEvent) -> void:
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT and event.pressed:
advance_dialogue()
accept_event()
elif event is InputEventKey and event.pressed and not event.echo \
and event.keycode in [KEY_ENTER, KEY_KP_ENTER, KEY_SPACE]:
advance_dialogue()
accept_event()
func _split_dialogue_pages(text: String) -> Array[String]:
var normalized := text.strip_edges()
if normalized.is_empty():
return []
var pages: Array[String] = []
var current := ""
for word in normalized.split(" ", false):
var candidate := word if current.is_empty() else "%s %s" % [current, word]
var has_natural_break := current.length() >= DIALOGUE_PAGE_MIN_CHARS \
and _ends_dialogue_phrase(word)
if candidate.length() > DIALOGUE_PAGE_MAX_CHARS and not current.is_empty():
pages.append(current)
current = word
elif has_natural_break:
pages.append(candidate)
current = ""
else:
current = candidate
if not current.is_empty():
pages.append(current)
return pages
func _ends_dialogue_phrase(word: String) -> bool:
return word.ends_with(",") or word.ends_with(";") or word.ends_with(":") \
or word.ends_with(".") or word.ends_with("?") or word.ends_with("!")
func set_choices_pending(pending: bool) -> void:
for child in choice_container.get_children():
if child is BaseButton: