extends Control ## Pause menu (ESC): resume, controls, fullscreen, mouse sensitivity, quit. ## Pauses the scene tree while open; runs with PROCESS_MODE_ALWAYS so it still ## gets input. const UITheme = preload("res://scripts/ui/theme.gd") const CONTROLS := [ ["W A S D", "Move"], ["Shift", "Sprint"], ["Space", "Jump / swim up"], ["Mouse", "Look"], ["E", "Interact / pick up / add fuel"], ["Left click", "Use tool / place"], ["Right click (holding food)", "Eat"], ["R", "Rotate piece"], ["1 - 8, wheel", "Hotbar"], ["Tab", "Inventory & crafting"], ["Shift + click", "Move stack to/from a chest"], ["Right click", "Split stack"], ["Esc", "This menu (game autosaves every 2 min)"], ] var player: CharacterBody3D var _main: VBoxContainer var _controls: VBoxContainer var _quit_btn: Button var _quit_armed := false func bind(p: CharacterBody3D) -> void: player = p process_mode = Node.PROCESS_MODE_ALWAYS set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) mouse_filter = Control.MOUSE_FILTER_STOP visible = false var dim := ColorRect.new() dim.color = Color(0, 0, 0, 0.55) dim.set_anchors_preset(Control.PRESET_FULL_RECT) dim.mouse_filter = Control.MOUSE_FILTER_IGNORE add_child(dim) var center := CenterContainer.new() center.set_anchors_preset(Control.PRESET_FULL_RECT) center.mouse_filter = Control.MOUSE_FILTER_IGNORE add_child(center) var panel := UITheme.panel() panel.custom_minimum_size = Vector2(380, 0) center.add_child(panel) var stack := VBoxContainer.new() panel.add_child(stack) # Main page. _main = VBoxContainer.new() _main.add_theme_constant_override("separation", 10) stack.add_child(_main) var title := UITheme.label("Paused", 30) title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER _main.add_child(title) _main.add_child(_button("Resume", close)) _main.add_child(_button("Controls", _show_controls.bind(true))) var fs := CheckButton.new() fs.text = "Fullscreen" fs.focus_mode = Control.FOCUS_NONE fs.button_pressed = DisplayServer.window_get_mode() == DisplayServer.WINDOW_MODE_FULLSCREEN fs.add_theme_font_size_override("font_size", 18) fs.toggled.connect(_on_fullscreen) _main.add_child(fs) var sens_row := HBoxContainer.new() sens_row.add_child(UITheme.label("Mouse sensitivity", 16)) var sens := HSlider.new() sens.min_value = 0.0005 sens.max_value = 0.006 sens.step = 0.0001 sens.value = player.mouse_sensitivity sens.size_flags_horizontal = Control.SIZE_EXPAND_FILL sens.focus_mode = Control.FOCUS_NONE sens.value_changed.connect(func(v: float): player.mouse_sensitivity = v) sens_row.add_child(sens) _main.add_child(sens_row) var saved: bool = Saves.has_world() if saved: _main.add_child(_button("Save & Quit to Title", _on_quit_to_title)) _quit_btn = _button("Save & Quit to Desktop", _on_quit) else: _quit_btn = _button("Quit to Desktop", _on_quit) _main.add_child(_quit_btn) var note_text := "Autosaves every 2 minutes. World: %s" % Saves.read_meta(Saves.current_id).get("name", "?") if saved \ else "Not playing a saved world: progress won't be kept." var note := UITheme.label(note_text, 13, UITheme.TEXT_DIM) note.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER _main.add_child(note) # Controls page. _controls = VBoxContainer.new() _controls.add_theme_constant_override("separation", 6) _controls.visible = false stack.add_child(_controls) var ct := UITheme.label("Controls", 26) ct.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER _controls.add_child(ct) var grid := GridContainer.new() grid.columns = 2 grid.add_theme_constant_override("h_separation", 24) for row in CONTROLS: grid.add_child(UITheme.label(row[0], 16, UITheme.SELECTED)) grid.add_child(UITheme.label(row[1], 16)) _controls.add_child(grid) _controls.add_child(_button("Back", _show_controls.bind(false))) func _button(text: String, cb: Callable) -> Button: var b := Button.new() b.text = text b.focus_mode = Control.FOCUS_NONE b.custom_minimum_size = Vector2(0, 42) b.add_theme_font_size_override("font_size", 18) b.pressed.connect(cb) return b func is_open() -> bool: return visible func open() -> void: visible = true _show_controls(false) _disarm_quit() player.ui_open = true get_tree().paused = true Input.mouse_mode = Input.MOUSE_MODE_VISIBLE func close() -> void: visible = false player.ui_open = false get_tree().paused = false Input.mouse_mode = Input.MOUSE_MODE_CAPTURED ## ESC on the controls page goes back; on the main page it resumes. func back() -> void: if _controls.visible: _show_controls(false) else: close() func _show_controls(on: bool) -> void: _controls.visible = on _main.visible = not on func _on_fullscreen(on: bool) -> void: DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN if on else DisplayServer.WINDOW_MODE_WINDOWED) func _on_quit() -> void: if Saves.has_world(): player.get_parent().save_now() get_tree().quit() return # Unsaved session: ask twice so nobody loses a run by accident. if not _quit_armed: _quit_armed = true _quit_btn.text = "Click again to quit" return get_tree().quit() func _on_quit_to_title() -> void: player.get_parent().save_now() get_tree().paused = false Saves.current_id = "" get_tree().auto_accept_quit = true get_tree().change_scene_to_file("res://scenes/title.tscn") func _disarm_quit() -> void: _quit_armed = false _quit_btn.text = "Save & Quit to Desktop" if Saves.has_world() else "Quit to Desktop"