- Queue management: enqueue on mid-display arrival, drain in order, MAX_QUEUE_DEPTH=8 cap, no-overwrite contract (P0 #477) - Character colours: detective=#c8e0ff (cool blue), smuggler=#f0c870 (warm amber), fallback to neutral insert text - Typography: italic via BBCode [i] tags; font size 13px (smaller than dialogue) - Positioning: bottom-left of viewport, 420×120px, 80px bottom margin above verb list - main.gd: _get_active_character_type() reads player entity kind.data.character_type; passed to show_monologue() and _on_confrontation_monologue() - gdUnit4 tests: queue order, depth cap, no-overwrite, BBCode output, colour mapping, fade timer integration Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
90 lines
2.8 KiB
GDScript
90 lines
2.8 KiB
GDScript
extends Control
|
|
|
|
# Internal monologue display (per D-016)
|
|
# Queue-managed text overlay — bottom-left of viewport, italic, character-coloured.
|
|
# Spec: Sprint 14 briefing (D-032, D-055).
|
|
#
|
|
# Queue contract (P0, #477):
|
|
# - Never overwrites a mid-display line.
|
|
# - New arrivals queue up to MAX_QUEUE_DEPTH; deeper arrivals are silently dropped.
|
|
# - When the current line fades out, the next queued line starts immediately.
|
|
|
|
const MAX_QUEUE_DEPTH: int = 8
|
|
|
|
# Character text colours (D-048 insert palette, Sprint 14 briefing)
|
|
const COLOR_DETECTIVE: Color = Color("#c8e0ff") # Cool blue-white — analytical
|
|
const COLOR_SMUGGLER: Color = Color("#f0c870") # Warm amber — street-smart
|
|
const COLOR_DEFAULT: Color = Color("#c8d0e0") # Neutral fallback (insert text)
|
|
|
|
@onready var text_panel: PanelContainer = $PanelContainer
|
|
@onready var text_label: RichTextLabel = $PanelContainer/MarginContainer/RichTextLabel
|
|
|
|
var _queue: Array[Dictionary] = [] # {text, duration, character_type}
|
|
var _displaying: bool = false
|
|
var _fade_timer: float = 0.0
|
|
var _current_duration: float = 0.0
|
|
var _active_tween: Tween = null
|
|
|
|
|
|
func _ready() -> void:
|
|
text_panel.modulate.a = 0.0
|
|
_displaying = false
|
|
|
|
|
|
func _process(delta: float) -> void:
|
|
if not _displaying:
|
|
return
|
|
_fade_timer += delta
|
|
if _fade_timer >= _current_duration:
|
|
_fade_out()
|
|
|
|
|
|
# Show a monologue line. If a line is already displaying, enqueue it instead.
|
|
# character_type: "detective", "smuggler", or "" (default colour).
|
|
func show_monologue(text: String, duration: float = 5.0, character_type: String = "") -> void:
|
|
if _displaying:
|
|
if _queue.size() < MAX_QUEUE_DEPTH:
|
|
_queue.append({text = text, duration = duration, character_type = character_type})
|
|
return
|
|
_display(text, duration, character_type)
|
|
|
|
|
|
func _display(text: String, duration: float, character_type: String) -> void:
|
|
_displaying = true
|
|
_fade_timer = 0.0
|
|
_current_duration = duration
|
|
|
|
var color_hex: String = _color_for_character(character_type).to_html(false)
|
|
text_label.text = "[i][color=#%s]%s[/color][/i]" % [color_hex, text]
|
|
|
|
if _active_tween and _active_tween.is_valid():
|
|
_active_tween.kill()
|
|
_active_tween = create_tween()
|
|
_active_tween.tween_property(text_panel, "modulate:a", 1.0, 0.3)
|
|
|
|
|
|
func _fade_out() -> void:
|
|
if not _displaying:
|
|
return
|
|
_displaying = false
|
|
|
|
if _active_tween and _active_tween.is_valid():
|
|
_active_tween.kill()
|
|
_active_tween = create_tween()
|
|
_active_tween.tween_property(text_panel, "modulate:a", 0.0, 0.5)
|
|
_active_tween.tween_callback(_on_fade_complete)
|
|
|
|
|
|
func _on_fade_complete() -> void:
|
|
if _queue.is_empty():
|
|
return
|
|
var next: Dictionary = _queue.pop_front()
|
|
_display(next.text, next.duration, next.character_type)
|
|
|
|
|
|
func _color_for_character(character_type: String) -> Color:
|
|
match character_type:
|
|
"detective": return COLOR_DETECTIVE
|
|
"smuggler": return COLOR_SMUGGLER
|
|
_: return COLOR_DEFAULT
|