extends Node ## Autoload coordinator for overlay MetaScreens (#618, #680). ## Scene-root screens (main_menu, character_creation) do NOT push onto this stack. ## Only overlay screens (settings, debug_console, bug_report, loading_screen) push. ## ## Autoload parse-order: MetaScreen is a class_name type — referenced here only ## inside method bodies called at runtime, never at the top level or in _ready(). signal meta_active_changed(active: bool) var _stack: Array = [] # Array[MetaScreen] — untyped per parse-order rule func push(screen) -> void: # screen: MetaScreen if screen in _stack: return _stack.append(screen) screen.closed.connect(_on_screen_closed.bind(screen), CONNECT_ONE_SHOT) if _stack.size() == 1: meta_active_changed.emit(true) if screen.pauses_sim: _request_pause(true) func pop() -> void: if _stack.is_empty(): return _stack[-1].close() func top(): # returns MetaScreen or null return _stack[-1] if not _stack.is_empty() else null func is_active() -> bool: return not _stack.is_empty() ## Handle ESC key. Call from main.gd before HudGroups ESC handling. ## Returns true if the event was consumed (callers must return after). ## ## A screen on the stack always consumes the event. `closable_by_escape = false` ## means "I refuse to close on ESC" — not "pass the event through to the ## implant/gameplay layer." Otherwise an un-escapable screen (e.g. LoadingScreen) ## would leak ESC to main.gd and open the settings dialog behind it. func handle_escape() -> bool: var t = top() if t == null: return false if not t.closable_by_escape: t.on_escape() return true if t.on_escape(): return true t.close() return true func _on_screen_closed(screen) -> void: # screen: MetaScreen _stack.erase(screen) if screen.pauses_sim and not _any_pausing(): _request_pause(false) if _stack.is_empty(): meta_active_changed.emit(false) func _any_pausing() -> bool: for s in _stack: if s.pauses_sim: return true return false func _request_pause(pause: bool) -> void: if SimBridge.state != SimBridge.ConnectionState.CONNECTED: return var action = InputMapper.Action.PAUSE if pause else InputMapper.Action.UNPAUSE SimBridge.send_input({"action": action, "timestamp_msec": Time.get_ticks_msec()})