Sprint 24 Signal — three client tickets delivering the player-facing storyteller feedback loop: - #588: Character archetype select screen between New Game and session start. Two-card UI (Smuggler/Detective), keyboard+mouse, ESC cancels. GameState.character_archetype persisted and sent in StartupMessage. PROTOCOL_VERSION bumped to 19. - #590: Triangle crisis event consumer. Decodes triangle_crisis_events from snapshot, fires sfx_monologue_chime_urgent once per triangle per session via AudioManager.CHIME_ACTIVATION. - #592: News ticker HUD element. Scrolling marquee on UILayer, visible only when current_ticker is present in snapshot (Last Shift zone). Zero-arg update_from_state reads from GameState.current_snapshot. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
53 lines
1.6 KiB
GDScript
53 lines
1.6 KiB
GDScript
extends Control
|
|
## #592: News ticker — scrolling horizontal headline bar, active in The Last Shift zone.
|
|
## Lives on UILayer (z-layer 7 per D-049). Not suppressed by insert_active (D-013):
|
|
## the ticker is a real-world screen the player can see regardless of insert state.
|
|
## Text scrolls left at SCROLL_SPEED px/sec. When current_ticker is null, hides.
|
|
|
|
const BG_COLOR := Color(0.05, 0.05, 0.07, 0.75)
|
|
const TEXT_COLOR := Color(0.784, 0.816, 0.878, 1.0) # INSERT_COLOR_TEXT
|
|
const FONT_SIZE := 13
|
|
const SCROLL_SPEED := 60.0 # pixels per second
|
|
const BAR_HEIGHT := 28
|
|
|
|
@onready var _label: Label = $TickerLabel
|
|
|
|
var _text: String = ""
|
|
var _scroll_x: float = 0.0
|
|
var _content_width: float = 0.0
|
|
|
|
|
|
func _ready() -> void:
|
|
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
_label.add_theme_font_size_override("font_size", FONT_SIZE)
|
|
_label.add_theme_color_override("font_color", TEXT_COLOR)
|
|
visible = false
|
|
|
|
|
|
func update_from_state() -> void:
|
|
var ticker: Variant = GameState.current_snapshot.get("current_ticker")
|
|
if ticker == null or not ticker is Dictionary:
|
|
visible = false
|
|
return
|
|
var new_text: String = ticker.get("text", "")
|
|
if new_text.is_empty():
|
|
visible = false
|
|
return
|
|
if new_text != _text:
|
|
_text = new_text
|
|
_label.text = _text
|
|
# Reset scroll to start from the right edge on new headline.
|
|
_content_width = _label.get_minimum_size().x
|
|
_scroll_x = size.x
|
|
visible = true
|
|
|
|
|
|
func _process(delta: float) -> void:
|
|
if not visible:
|
|
return
|
|
_scroll_x -= SCROLL_SPEED * delta
|
|
# Restart from right edge when text has fully exited left.
|
|
if _scroll_x + _content_width < 0.0:
|
|
_scroll_x = size.x
|
|
_label.position.x = _scroll_x
|