extends Node # Автолоад «Overlay»: общие панели поверх любой карты — ввод ника (клавиша N, первый запуск в хабе) # и таблица лидеров (Tab). Пока открыта панель ника, Net.input_blocked = true: игрок (player.gd) стоит и не # обрабатывает игровые клавиши. Это отдельный флаг, НЕ player.input_frozen (его ведёт гонка на отсчёте). const TEXT := Color(0.92, 0.88, 0.8) const DIM := Color(0.62, 0.6, 0.56) const MINE := Color(0.96, 0.76, 0.44) var nick_open := false var lb_open := false var _first_run := false var _nick_layer: CanvasLayer var _nick_title: Label var _edit: LineEdit var _lb_layer: CanvasLayer var _lb_title: Label var _lb_tabs: HBoxContainer var _lb_holder: Control var _lb_hint: Label var _lb_view: LbView var _lb_maps: Array = [] var _lb_index := 0 var _lb_for_map := "" func _ready() -> void: process_mode = Node.PROCESS_MODE_ALWAYS _build_nick_panel() _build_lb_panel() # ---------- ввод ника ---------- func _build_nick_panel() -> void: _nick_layer = CanvasLayer.new() _nick_layer.layer = 20 _nick_layer.visible = false add_child(_nick_layer) var root := Control.new() root.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) root.mouse_filter = Control.MOUSE_FILTER_IGNORE _nick_layer.add_child(root) var panel := PanelContainer.new() panel.add_theme_stylebox_override("panel", LbView.panel_style(26)) root.add_child(panel) panel.set_anchors_and_offsets_preset(Control.PRESET_CENTER) panel.grow_horizontal = Control.GROW_DIRECTION_BOTH panel.grow_vertical = Control.GROW_DIRECTION_BOTH var box := VBoxContainer.new() box.add_theme_constant_override("separation", 14) panel.add_child(box) _nick_title = LbView.make_label("Введите ник", 28, TEXT) _nick_title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER box.add_child(_nick_title) _edit = LineEdit.new() _edit.max_length = Net.NICK_MAX _edit.custom_minimum_size = Vector2(320, 40) _edit.add_theme_font_size_override("font_size", 22) _edit.alignment = HORIZONTAL_ALIGNMENT_CENTER _edit.placeholder_text = "до %d символов" % Net.NICK_MAX _edit.context_menu_enabled = false var sb := StyleBoxFlat.new() sb.bg_color = Color(0.015, 0.018, 0.02, 0.95) sb.border_color = Color(0.45, 0.30, 0.16, 0.9) sb.set_border_width_all(1) sb.set_content_margin_all(8) _edit.add_theme_stylebox_override("normal", sb) _edit.add_theme_stylebox_override("focus", sb) _edit.add_theme_color_override("font_color", TEXT) box.add_child(_edit) _edit.focus_next = _edit.get_path() # Tab не уводит фокус _edit.focus_previous = _edit.get_path() _edit.text_submitted.connect(_on_nick_submitted) _edit.gui_input.connect(_on_edit_gui_input) var hint := LbView.make_label("Enter — подтвердить · Esc — пропустить", 16, DIM) hint.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER box.add_child(hint) func open_nick(first_run := false) -> void: if nick_open: return nick_open = true _first_run = first_run Net.input_blocked = true if lb_open: close_lb() _nick_title.text = "Введите ник" if (first_run or Net.my_nick == "") else "Смена ника" _edit.text = Net.my_nick _edit.caret_column = _edit.text.length() Input.mouse_mode = Input.MOUSE_MODE_VISIBLE _nick_layer.visible = true _edit.grab_focus.call_deferred() # открывающая клавиша уже обработана и в поле не попадает func _close_nick(apply: bool) -> void: if not nick_open: return if apply: Net.set_nick(_edit.text) elif _first_run: Net.skip_nick() nick_open = false Net.input_blocked = false _nick_layer.visible = false _edit.release_focus() Input.mouse_mode = Input.MOUSE_MODE_CAPTURED func _on_nick_submitted(_text: String) -> void: _close_nick(true) func _on_edit_gui_input(event: InputEvent) -> void: if event.is_action_pressed("ui_cancel"): _edit.accept_event() _close_nick(false) # ---------- таблица лидеров ---------- func _build_lb_panel() -> void: _lb_layer = CanvasLayer.new() _lb_layer.layer = 14 _lb_layer.visible = false add_child(_lb_layer) var root := Control.new() root.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) root.mouse_filter = Control.MOUSE_FILTER_IGNORE _lb_layer.add_child(root) var panel := PanelContainer.new() panel.add_theme_stylebox_override("panel", LbView.panel_style(18)) panel.mouse_filter = Control.MOUSE_FILTER_IGNORE root.add_child(panel) # справа по центру: сверху остаётся таймер гонки, в центре — отсчёт, слева — место и ворота panel.anchor_left = 1.0 panel.anchor_right = 1.0 panel.anchor_top = 0.5 panel.anchor_bottom = 0.5 panel.offset_right = -48.0 panel.grow_horizontal = Control.GROW_DIRECTION_BEGIN panel.grow_vertical = Control.GROW_DIRECTION_BOTH var box := VBoxContainer.new() box.add_theme_constant_override("separation", 8) panel.add_child(box) _lb_title = LbView.make_label("", 24, TEXT) box.add_child(_lb_title) _lb_tabs = HBoxContainer.new() _lb_tabs.add_theme_constant_override("separation", 14) box.add_child(_lb_tabs) var hold := VBoxContainer.new() hold.mouse_filter = Control.MOUSE_FILTER_IGNORE box.add_child(hold) _lb_holder = hold _lb_hint = LbView.make_label("", 14, DIM) box.add_child(_lb_hint) func _lb_map_list() -> Array: if Net.TIMED_MAPS.has(Net.current_map): return [Net.current_map] return Net.TIMED_MAPS.keys() # хаб и карты без рейтинга: вкладки по всем картам func open_lb() -> void: if lb_open or nick_open: return lb_open = true _lb_for_map = Net.current_map _lb_maps = _lb_map_list() _lb_index = 0 _lb_view = LbView.create(_lb_maps[0], 18) _lb_holder.add_child(_lb_view) _lb_layer.visible = true _lb_refresh() func close_lb() -> void: if not lb_open: return lb_open = false _lb_layer.visible = false if _lb_view != null: _lb_holder.remove_child(_lb_view) _lb_view.queue_free() _lb_view = null func _lb_refresh() -> void: var m: String = _lb_maps[_lb_index] _lb_title.text = "ЛУЧШИЕ ВРЕМЕНА · " + Net.MAP_TITLES.get(m, m).to_upper() if _lb_maps.size() == 1 else "ЛУЧШИЕ ВРЕМЕНА" for c in _lb_tabs.get_children(): c.queue_free() _lb_tabs.visible = _lb_maps.size() > 1 for i in _lb_maps.size(): _lb_tabs.add_child(LbView.make_label(Net.MAP_TITLES.get(_lb_maps[i], _lb_maps[i]), 18, MINE if i == _lb_index else DIM)) _lb_hint.text = ("A / D — карта · " if _lb_maps.size() > 1 else "") + "Tab / Esc — закрыть" _lb_view.set_map(m) Net.request_board(m) func _process(_delta: float) -> void: if lb_open and _lb_for_map != Net.current_map: close_lb() # перешли на другую карту через портал func _unhandled_input(event: InputEvent) -> void: if nick_open: if event.is_action_pressed("ui_cancel"): _close_nick(false) return if event.is_echo(): return if event.is_action_pressed("nick"): open_nick(false) elif event.is_action_pressed("leaderboard"): if lb_open: close_lb() else: open_lb() elif lb_open and event.is_action_pressed("ui_cancel"): close_lb() elif lb_open and _lb_maps.size() > 1: var d := 0 if event.is_action_pressed("move_left"): d = -1 elif event.is_action_pressed("move_right"): d = 1 if d != 0: _lb_index = posmod(_lb_index + d, _lb_maps.size()) _lb_refresh()