Files
settled-reach/client/scripts/snapshot_consumers.gd
T
jpmschweitzer 760953aeba feat(ui): atlas implant panel — system picker, orbital diagram, body nav (#834)
3-level atlas navigation as an implant/map/atlas FULLSCREEN app (D-170 z=20):
system picker (◄ ► cycle, Enter opens orbital) → orbital diagram (star centre,
bodies grouped by orbit_index with moons sub-orbiting parents, stations as
markers, click-to-open body or mini station panel) → body entry (data sheet,
Enter stub for #835 heightmap viewer, Esc back). Composed from the ImplantPanel
component library (D-169).

Wires the panel into hud.tscn, adds an A-key toggle in main.gd, propagates
insert_state via snapshot_consumers. Extends generate-star-map-data.py to emit
per-system orbit_bodies + stations arrays (with currency_zone, atmosphere,
population, terrain_reference, etc.) from systems.db; star_map_data.json
regenerated deterministically.

Per D-191 §6, Phase 3.
2026-04-14 17:24:50 +02:00

211 lines
7.2 KiB
GDScript

class_name SnapshotConsumers
## Non-dialogue snapshot consumers and event handlers extracted from main.gd (#775).
##
## Registered with SnapshotEventRouter; reads from GameState directly.
## UI node references passed via init(). All methods are zero-argument
## callables compatible with SnapshotEventRouter.
const LISTENING_FOCUS_TICKS: int = 30 # D-071: stationary ticks before ListeningFocus boost
var monologue_display: Node = null
var dialogue_box: Node = null
var examine_display: Node = null
var loading_screen: Node = null
var debug_console: Node = null
var cursor_renderer: Node = null
var interaction_list: Node = null
var interaction_prompt: Node = null
var minimap: Node = null
var star_map: Node = null
var economics_panel: Node = null # #824: economics monitor (D-181)
var atlas_panel: Node = null # #834: atlas implant panel (D-191)
var _screen_flash_fn: Callable # Callable(color: Color, duration: float)
var _last_monologue_tick: int = -1
var _known_recognition_ids: Dictionary = {}
var _known_triangle_ids: Dictionary = {}
var _current_zone: String = ""
func init(refs: Dictionary, screen_flash: Callable) -> SnapshotConsumers:
monologue_display = refs.get("monologue_display")
dialogue_box = refs.get("dialogue_box")
examine_display = refs.get("examine_display")
loading_screen = refs.get("loading_screen")
debug_console = refs.get("debug_console")
cursor_renderer = refs.get("cursor_renderer")
interaction_list = refs.get("interaction_list")
interaction_prompt = refs.get("interaction_prompt")
minimap = refs.get("minimap")
star_map = refs.get("star_map")
economics_panel = refs.get("economics_panel")
atlas_panel = refs.get("atlas_panel")
_screen_flash_fn = screen_flash
return self
# OQ-07 (#522): Propagate insert state to all z-layer-6 display nodes.
func propagate_insert_state() -> void:
var insert_state := GameState.insert_active
if cursor_renderer:
cursor_renderer.set_insert_active(insert_state)
if interaction_list:
interaction_list.set_insert_active(insert_state)
if interaction_prompt:
interaction_prompt.set_insert_active(insert_state)
if minimap:
minimap.set_insert_active(insert_state)
if star_map:
star_map.set_insert_active(insert_state)
if economics_panel:
economics_panel.set_insert_active(insert_state)
if atlas_panel:
atlas_panel.set_insert_active(insert_state)
# D-057: Update interaction list from game state.
# Suppress during dialogue — player is in conversation, verb list is noise.
func update_interaction_list() -> void:
if not interaction_list:
return
if dialogue_box and dialogue_box.is_dialogue_active():
if interaction_list.is_showing():
interaction_list.hide_list()
else:
interaction_list.update_from_state()
# D-018 #125: Play close-range sound events — fired once per snapshot tick.
func play_close_sound_events() -> void:
for evt in GameState.close_sound_events:
if not evt is Dictionary or not evt.has("x") or not evt.has("y"):
continue
AudioManager.play_sound_event(
evt.get("event_type", ""), Vector2(float(evt.x), float(evt.y))
)
GameState.close_sound_events = []
# D-067: Recognition chime — fires sfx_monologue_chime when a fog entity
# enters the cognitive delay recognition queue for the first time.
func play_recognition_chimes() -> void:
for rec in GameState.pending_recognitions:
if not rec is Dictionary or not rec.has("entity_id"):
continue
var eid: int = rec.entity_id
if not _known_recognition_ids.has(eid):
_known_recognition_ids[eid] = true
AudioManager.play(AudioManager.CHIME_RECOGNITION)
# #590 D-072/D-089: Triangle activation consumer.
func handle_triangle_crisis_events() -> void:
var events: Array = GameState.current_snapshot.get("triangle_crisis_events", [])
for ev in events:
if not ev is Dictionary or not ev.has("triangle_id"):
continue
var tid: int = ev.triangle_id
if not _known_triangle_ids.has(tid):
_known_triangle_ids[tid] = true
AudioManager.play(AudioManager.CHIME_ACTIVATION, AudioManager.BUS_UI_SOUNDS)
# D-073 (#529): Zone ambient crossfade.
func update_zone() -> void:
var zone := GameState.current_zone_id
if zone != _current_zone:
_current_zone = zone
AudioManager.set_zone(zone)
# D-071 (#530): ListeningFocus boost — World SFX +2.5dB when stationary 30+ ticks.
func update_listening_focus() -> void:
var current_dip := AudioManager.get_active_dip()
var threshold_met := GameState.stationary_ticks >= LISTENING_FOCUS_TICKS
if threshold_met and current_dip == "":
AudioManager.apply_dip("listening_focus")
elif not threshold_met and current_dip == "listening_focus":
AudioManager.clear_dip()
# Consume-once per tick: show monologue text, then clear.
func consume_monologue() -> void:
if GameState.current_monologue == null or not monologue_display:
return
if GameState.current_tick == _last_monologue_tick:
return
_last_monologue_tick = GameState.current_tick
var mono: Dictionary = GameState.current_monologue
monologue_display.show_monologue(
mono.get("text", ""),
mono.get("duration_seconds", 5.0),
mono.get("priority", 2),
mono.get("is_urgent", false)
)
# #502: Amber flash on room reset
var mono_id: String = mono.get("id", "")
if mono_id.begins_with("room_reset"):
_screen_flash_fn.call(Constants.ENTITY_COLOR_POI, 0.15)
GameState.current_monologue = null
# #554/#257: Show save/load result notification; hide loading screen on load complete.
func consume_save_result() -> void:
if GameState.save_result == null:
return
var result: Dictionary = GameState.save_result
GameState.save_result = null
if loading_screen:
loading_screen.hide_loading()
var msg: String
if result.get("success", false):
if result.get("kind", "") == "save":
msg = UIStrings.get_text("notifications.save_complete")
else:
msg = UIStrings.get_text("notifications.load_complete")
else:
if result.get("kind", "") == "save":
msg = UIStrings.get_text("notifications.save_failed")
else:
msg = UIStrings.get_text("notifications.load_failed")
if monologue_display:
monologue_display.show_notification(msg)
# #581: Forward debug_response from server to the debug console.
func consume_debug_response() -> void:
if GameState.debug_response == null or not debug_console:
return
debug_console.append_response(GameState.debug_response)
GameState.debug_response = null
# #824: Forward economy_snapshot from server to the economics panel (D-181).
func consume_economy_snapshot() -> void:
if GameState.economy_snapshot == null or not economics_panel:
return
if economics_panel.has_method("receive_economy_data"):
economics_panel.receive_economy_data(GameState.economy_snapshot)
GameState.economy_snapshot = null
# #174: Consume examine result — show overlay when server sends character-filtered observation.
func consume_examine_result() -> void:
if GameState.current_examine_result == null or not examine_display:
return
var result: Dictionary = GameState.current_examine_result
if dialogue_box and dialogue_box.is_dialogue_active():
if examine_display.has_method("dismiss"):
examine_display.dismiss()
else:
if examine_display.has_method("show_result"):
examine_display.show_result(result)
GameState.current_examine_result = null
## Clear session-scoped recognition state (call on teleport / room change).
func clear_recognition_state() -> void:
_known_recognition_ids.clear()
_known_triangle_ids.clear()