Improve CCS dialogue layout

This commit is contained in:
hz
2026-08-06 04:41:44 -05:00
parent 4792ff2620
commit ee8e8cf787
4 changed files with 205 additions and 37 deletions
+89 -17
View File
@@ -9,6 +9,8 @@ const CHOICE_NORMAL := preload("res://assets/ccs_cases_2/images/doctor/dialogue-
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)
const CHOICES_PER_PAGE := 4
const RESPONSE_HOLD_SECONDS := 1.25
@onready var hour_hand: Node2D = $ClockMount/HourHand
@onready var minute_hand: Node2D = $ClockMount/MinuteHand
@@ -16,17 +18,27 @@ const CHOICE_CROP := Rect2(80.0, 200.0, 1610.0, 485.0)
@onready var patient: TextureRect = $Patient
@onready var dialogue_bubble: Control = $DialogueBubble
@onready var dialogue_text: Label = $DialogueBubble/Text
@onready var choice_container: GridContainer = $DialogueChoices
@onready var choice_area: Control = $DialogueChoiceArea
@onready var choice_container: HBoxContainer = $DialogueChoiceArea/DialogueChoices
@onready var choice_pager: HBoxContainer = $DialogueChoiceArea/ChoicePager
@onready var previous_page_button: Button = $DialogueChoiceArea/ChoicePager/Previous
@onready var page_label: Label = $DialogueChoiceArea/ChoicePager/Page
@onready var next_page_button: Button = $DialogueChoiceArea/ChoicePager/Next
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
func _ready() -> void:
previous_page_button.pressed.connect(_change_choice_page.bind(-1))
next_page_button.pressed.connect(_change_choice_page.bind(1))
_update_clock()
_schedule_patient_blink()
hide_patient_dialogue()
@@ -83,10 +95,12 @@ 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_text.visible_ratio = 0.0
dialogue_bubble.visible = true
_resize_dialogue_bubble()
call_deferred("_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)
@@ -96,6 +110,8 @@ 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
pending_choices = []
@@ -105,10 +121,8 @@ func is_patient_dialogue_visible() -> bool:
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)
var target_height := clampf(82.0 + line_count * 29.0, 150.0, 310.0)
dialogue_bubble.size.y = target_height
@@ -142,8 +156,20 @@ 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)
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
show_dialogue_choices(choices)
@@ -151,19 +177,33 @@ func set_choices_pending(pending: bool) -> void:
for child in choice_container.get_children():
if child is BaseButton:
child.disabled = pending
previous_page_button.disabled = pending or choice_page <= 0
next_page_button.disabled = pending or choice_page >= _choice_page_count() - 1
func show_dialogue_choices(options: Array) -> void:
hide_dialogue_choices()
var row_count := ceili(float(options.size()) / 2.0)
var stack_height := row_count * 97.0 + 8.0
choice_container.offset_top = -4.0 - stack_height
for index in options.size():
var option: Dictionary = options[index]
available_choices = options.duplicate(true)
choice_page = 0
_render_choice_page()
func _render_choice_page() -> void:
_clear_choice_buttons()
var page_count := _choice_page_count()
if page_count == 0:
choice_area.visible = false
return
choice_page = clampi(choice_page, 0, page_count - 1)
var start_index := choice_page * CHOICES_PER_PAGE
var end_index := mini(start_index + CHOICES_PER_PAGE, available_choices.size())
for index in range(start_index, end_index):
var option: Dictionary = available_choices[index]
var choice_id := str(option.get("id", ""))
var option_text := str(option.get("text", ""))
var button := TextureButton.new()
button.custom_minimum_size = Vector2(440.0, 105.0)
button.set_meta("choice_id", choice_id)
button.custom_minimum_size = Vector2(350.0, 136.0)
button.ignore_texture_size = true
button.stretch_mode = TextureButton.STRETCH_SCALE
button.texture_normal = _crop_choice_texture(CHOICE_NORMAL)
@@ -181,24 +221,56 @@ func show_dialogue_choices(options: Array) -> void:
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_top = 12.0
label.offset_right = -48.0
label.offset_bottom = -14.0
label.offset_bottom = -12.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.add_theme_font_size_override("font_size", _choice_font_size(option_text))
label.text = option_text
label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
label.clip_text = true
label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
button.add_child(label)
choice_container.add_child(button)
choice_container.visible = not options.is_empty()
choice_pager.visible = page_count > 1
page_label.text = "Page %d of %d" % [choice_page + 1, page_count]
previous_page_button.disabled = choice_page <= 0
next_page_button.disabled = choice_page >= page_count - 1
choice_area.visible = true
choice_container.visible = true
func hide_dialogue_choices() -> void:
for child in choice_container.get_children():
child.queue_free()
_clear_choice_buttons()
available_choices = []
choice_page = 0
choice_container.visible = false
choice_pager.visible = false
choice_area.visible = false
func _clear_choice_buttons() -> void:
for child in choice_container.get_children():
choice_container.remove_child(child)
child.queue_free()
func _choice_page_count() -> int:
return ceili(float(available_choices.size()) / float(CHOICES_PER_PAGE))
func _change_choice_page(direction: int) -> void:
choice_page = clampi(choice_page + direction, 0, maxi(_choice_page_count() - 1, 0))
_render_choice_page()
func _choice_font_size(text: String) -> int:
if text.length() > 105:
return 15
if text.length() > 75:
return 16
return 17
func _crop_choice_texture(source: Texture2D) -> AtlasTexture: