extends Node2D ## Игровой слой: убежище в разрезе, вид сбоку. ## ## Связывает три части: модель (VaultGrid), отрисовку (VaultView) и HUD. ## Здесь же живёт контракт сохранения — сцена лежит в группе "persistent". ## ## Управление: ЛКМ — построить или снести, ПКМ/СКМ — тащить вид, ## колесо — зум, WASD и стрелки — прокрутка. ## Строки сводки ресурсов: ключ из VaultGrid.totals() → подпись и цвет. const STAT_ROWS := { "power": {"label": "Питание", "color": "#f2b134"}, "water": {"label": "Вода", "color": "#4aa8f2"}, "food": {"label": "Еда", "color": "#f2704a"}, } const HINT := "ЛКМ — строить · ПКМ — тащить вид · колесо — зум" @onready var _saves: SaveManagerService = get_node("/root/SaveManager") @onready var _vault: VaultView = $View @onready var _camera: VaultCamera = $Camera @onready var _stats: HBoxContainer = $Hud/Root/TopBar/Stats @onready var _tools: HBoxContainer = $Hud/Root/BottomAnchor/Row/BuildBar/Panel/Tools @onready var _status: Label = $Hud/Root/BottomAnchor/Row/BuildBar/Panel/Status var _grid := VaultGrid.new() var _selected_kind := "living" var _demolish := false var _stat_labels := {} func _ready() -> void: _grid.reset_to_starter() _vault.grid = _grid add_to_group(SaveManagerService.PERSIST_GROUP) _saves.begin_session(scene_file_path) _build_stats() _build_tools() _refresh_stats() _status.text = HINT func _process(_delta: float) -> void: _vault.set_ghost(_selected_kind, _cell_under_mouse(), _demolish) func _unhandled_input(event: InputEvent) -> void: if not (event is InputEventMouseButton): return var button := event as InputEventMouseButton if button.button_index != MOUSE_BUTTON_LEFT or not button.pressed: return _apply_at(_cell_under_mouse()) get_viewport().set_input_as_handled() # --- Строительство ---------------------------------------------------------- func _cell_under_mouse() -> Vector2i: return VaultGrid.cell_at(_vault.get_local_mouse_position()) func _apply_at(cell: Vector2i) -> void: if _demolish: if _grid.remove_at(cell.y, cell.x): _after_change("Комната снесена.") else: _status.text = "Здесь нечего сносить." return if _grid.place(_selected_kind, cell.y, cell.x): _after_change("Построено: %s" % VaultGrid.KINDS[_selected_kind]["label"]) else: _status.text = "Здесь строить нельзя: занято или вплотную к шахте." func _after_change(message: String) -> void: _vault.queue_redraw() _refresh_stats() _status.text = message # --- HUD -------------------------------------------------------------------- func _build_stats() -> void: for key: String in STAT_ROWS: var row := VBoxContainer.new() row.add_theme_constant_override("separation", 0) var caption := Label.new() caption.text = String(STAT_ROWS[key]["label"]) caption.add_theme_font_size_override("font_size", 14) caption.add_theme_color_override("font_color", Color("#8fa5ba")) row.add_child(caption) var value := Label.new() value.add_theme_font_size_override("font_size", 24) value.add_theme_color_override("font_color", Color(String(STAT_ROWS[key]["color"]))) row.add_child(value) _stat_labels[key] = value _stats.add_child(row) func _refresh_stats() -> void: var totals := _grid.totals() for key: String in _stat_labels: var amount := int(totals[key]) var label: Label = _stat_labels[key] label.text = "%+d" % amount # Дефицит важнее оформления: красим в тревожный цвет, а не в цвет ресурса. if amount < 0: label.add_theme_color_override("font_color", Color("#f2704a")) else: label.add_theme_color_override("font_color", Color(String(STAT_ROWS[key]["color"]))) func _build_tools() -> void: var group := ButtonGroup.new() for kind: String in VaultGrid.KINDS: var button := Button.new() button.text = String(VaultGrid.KINDS[kind]["label"]) button.toggle_mode = true button.button_group = group button.add_theme_font_size_override("font_size", 18) button.button_pressed = kind == _selected_kind button.pressed.connect(_on_kind_selected.bind(kind)) _tools.add_child(button) var separator := Control.new() separator.custom_minimum_size.x = 20 _tools.add_child(separator) var demolish := Button.new() demolish.text = "Снести" demolish.toggle_mode = true demolish.button_group = group demolish.add_theme_font_size_override("font_size", 18) demolish.pressed.connect(_on_demolish_selected) _tools.add_child(demolish) func _on_kind_selected(kind: String) -> void: _selected_kind = kind _demolish = false _status.text = HINT func _on_demolish_selected() -> void: _demolish = true _status.text = "Режим сноса: кликните по комнате." # --- Сохранение ------------------------------------------------------------- func save_data() -> Dictionary: return { "rooms": _grid.to_array(), "camera": { "x": _camera.position.x, "y": _camera.position.y, "zoom": _camera.zoom.x, }, } func load_data(data: Dictionary) -> void: _grid.from_array(data.get("rooms", [])) var camera_state: Dictionary = data.get("camera", {}) if not camera_state.is_empty(): _camera.position = Vector2( float(camera_state.get("x", 0.0)), float(camera_state.get("y", 0.0)) ) var level := float(camera_state.get("zoom", 1.0)) _camera.zoom = Vector2(level, level) _vault.queue_redraw() _refresh_stats() _status.text = "Убежище загружено."