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 economics_app: Node = null # #824: economics monitor (D-181) var atlas_app: Node = null # #844: atlas implant app (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") economics_app = refs.get("economics_app") atlas_app = refs.get("atlas_app") _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 not insert_state: if economics_app and economics_app.has_method("on_insert_deactivated"): economics_app.on_insert_deactivated() if atlas_app and atlas_app.has_method("on_insert_deactivated"): atlas_app.on_insert_deactivated() # 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 app (D-181). func consume_economy_snapshot() -> void: if GameState.economy_snapshot == null or not economics_app: return if economics_app.has_method("receive_economy_data"): economics_app.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()