588 lines
21 KiB
GDScript
588 lines
21 KiB
GDScript
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 := "ЛКМ — выбрать комнату · ПКМ — тащить вид · колесо — зум"
|
||
|
||
## Насколько мышь может сместиться между нажатием и отпусканием, чтобы это
|
||
## всё ещё считалось кликом, а не перетаскиванием вида.
|
||
const CLICK_SLACK := 6.0
|
||
|
||
## Режим курсора. По умолчанию сцена ничего не строит: строительство
|
||
## включается только выбором типа комнаты в нижней панели.
|
||
enum Mode { SELECT, BUILD, DEMOLISH }
|
||
|
||
@onready var _saves: SaveManagerService = get_node("/root/SaveManager")
|
||
@onready var _vault: VaultView = $View
|
||
@onready var _camera: VaultCamera = $Camera
|
||
@onready var _clock: GameClock = $Clock
|
||
@onready var _weather: WeatherSystem = $Weather
|
||
@onready var _sky: SkyView = $Sky
|
||
@onready var _surface: SurfaceView = $Surface
|
||
@onready var _weather_icon: WeatherIcon = $Hud/Root/TopRight/Row/ClockBar/Body/Climate/Icon
|
||
@onready var _weather_label: Label = $Hud/Root/TopRight/Row/ClockBar/Body/Climate/WeatherLabel
|
||
@onready var _temp_label: Label = $Hud/Root/TopRight/Row/ClockBar/Body/Climate/TempLabel
|
||
@onready var _wind_label: Label = $Hud/Root/TopRight/Row/ClockBar/Body/Climate/WindLabel
|
||
@onready var _stats: HBoxContainer = $Hud/Root/TopBar/Stats
|
||
@onready var _date_label: Label = $Hud/Root/TopRight/Row/ClockBar/Body/Moment/DateLabel
|
||
@onready var _time_label: Label = $Hud/Root/TopRight/Row/ClockBar/Body/Moment/TimeLabel
|
||
@onready var _flow_controls: HBoxContainer = $Hud/Root/TopRight/Row/ClockBar/Body/Controls
|
||
@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 _mode := Mode.SELECT
|
||
## Тип комнаты, который построится в режиме Mode.BUILD.
|
||
var _build_kind := ""
|
||
## Якорная ячейка выбранной комнаты; (-1, -1) — ничего не выбрано.
|
||
var _selected_cell := Vector2i(-1, -1)
|
||
var _stat_labels := {}
|
||
var _tool_group := ButtonGroup.new()
|
||
var _right_pressed := false
|
||
var _right_press_position := Vector2.ZERO
|
||
var _flow_group := ButtonGroup.new()
|
||
var _speed_group := ButtonGroup.new()
|
||
var _play_button: Button
|
||
var _pause_button: Button
|
||
var _speed_buttons := {}
|
||
|
||
|
||
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()
|
||
_build_flow_controls()
|
||
_refresh_stats()
|
||
|
||
_weather.attach_clock(_clock)
|
||
_weather.changed.connect(_on_weather_changed)
|
||
|
||
_clock.minute_passed.connect(_on_minute_passed)
|
||
_clock.flow_changed.connect(_refresh_flow)
|
||
_refresh_moment()
|
||
_refresh_flow()
|
||
_on_weather_changed(_weather.kind)
|
||
_register_console_commands()
|
||
|
||
_status.text = HINT
|
||
|
||
|
||
func _process(_delta: float) -> void:
|
||
# В режиме выбора призрака нет: пустой тип и (-1, -1) гасят подсветку.
|
||
if _mode == Mode.SELECT:
|
||
_vault.set_ghost("", Vector2i(-1, -1), false)
|
||
return
|
||
_vault.set_ghost(_build_kind, _cell_under_mouse(), _mode == Mode.DEMOLISH)
|
||
|
||
|
||
## ПКМ обслуживается здесь, а не в _unhandled_input: камера забирает эти
|
||
## события себе под перетаскивание вида. Событие не гасим — камера должна
|
||
## получить его как обычно.
|
||
func _input(event: InputEvent) -> void:
|
||
if not (event is InputEventMouseButton):
|
||
return
|
||
var button := event as InputEventMouseButton
|
||
if button.button_index != MOUSE_BUTTON_RIGHT:
|
||
return
|
||
|
||
if button.pressed:
|
||
_right_pressed = true
|
||
_right_press_position = button.position
|
||
return
|
||
|
||
if not _right_pressed:
|
||
return
|
||
_right_pressed = false
|
||
# Вид не двигали — значит это клик, а не перетаскивание.
|
||
if button.position.distance_to(_right_press_position) <= CLICK_SLACK:
|
||
_reset_to_select()
|
||
|
||
|
||
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_at_screen(button.position))
|
||
get_viewport().set_input_as_handled()
|
||
|
||
|
||
# --- Строительство ----------------------------------------------------------
|
||
|
||
func _cell_under_mouse() -> Vector2i:
|
||
return VaultGrid.cell_at(_vault.get_local_mouse_position())
|
||
|
||
|
||
## Ячейка под точкой экрана. Учитывает камеру: переводим из координат
|
||
## окна в локальные координаты вида.
|
||
func _cell_at_screen(screen_position: Vector2) -> Vector2i:
|
||
var local := _vault.get_global_transform_with_canvas().affine_inverse() * screen_position
|
||
return VaultGrid.cell_at(local)
|
||
|
||
|
||
func _apply_at(cell: Vector2i) -> void:
|
||
match _mode:
|
||
Mode.SELECT:
|
||
_select_at(cell)
|
||
Mode.DEMOLISH:
|
||
if _grid.remove_at(cell.y, cell.x):
|
||
_after_change("Комната снесена.")
|
||
else:
|
||
_status.text = "Здесь нечего сносить."
|
||
Mode.BUILD:
|
||
if _grid.place(_build_kind, cell.y, cell.x):
|
||
_after_change("Построено: %s" % VaultGrid.KINDS[_build_kind]["label"])
|
||
else:
|
||
_status.text = "Здесь строить нельзя: занято или вплотную к шахте."
|
||
|
||
|
||
func _after_change(message: String) -> void:
|
||
# Индексы комнат после изменения съезжают — выделение сбрасываем.
|
||
_set_selection(Vector2i(-1, -1))
|
||
_vault.queue_redraw()
|
||
_refresh_stats()
|
||
_status.text = message
|
||
|
||
|
||
# --- Выбор ------------------------------------------------------------------
|
||
|
||
func _select_at(cell: Vector2i) -> void:
|
||
var index := _grid.room_index_at(cell.y, cell.x)
|
||
if index < 0:
|
||
_set_selection(Vector2i(-1, -1))
|
||
_status.text = HINT
|
||
return
|
||
|
||
_set_selection(cell)
|
||
_status.text = _describe(_grid.rooms[index])
|
||
|
||
|
||
func _set_selection(cell: Vector2i) -> void:
|
||
_selected_cell = cell
|
||
_vault.set_selection(cell)
|
||
|
||
|
||
## Строка для нижней панели: что за комната, на каком этаже и её баланс.
|
||
func _describe(room: Dictionary) -> String:
|
||
var kind: Dictionary = VaultGrid.KINDS[String(room["kind"])]
|
||
var parts := PackedStringArray()
|
||
parts.append(String(kind["label"]))
|
||
parts.append("этаж %d" % (int(room["floor"]) + 1))
|
||
for key: String in STAT_ROWS:
|
||
var amount := int(kind[key])
|
||
if amount != 0:
|
||
parts.append("%s %+d" % [String(STAT_ROWS[key]["label"]).to_lower(), amount])
|
||
return " · ".join(parts)
|
||
|
||
|
||
# --- Команды консоли --------------------------------------------------------
|
||
|
||
## Команды живут здесь, а не в самой консоли: они управляют этой сценой и
|
||
## умирают вместе с ней — консоль сама выбросит их, когда Callable протухнет.
|
||
func _register_console_commands() -> void:
|
||
var console := get_node_or_null("/root/DevConsole")
|
||
if console == null:
|
||
return
|
||
|
||
console.register("stats", "текущее состояние партии", _cmd_stats)
|
||
console.register("time", "перевести часы", _cmd_time, "ЧЧ:ММ")
|
||
console.register("date", "сменить дату", _cmd_date, "ДД.ММ.ГГГГ")
|
||
console.register("speed", "скорость игры, 0 — пауза", _cmd_speed, "0..4")
|
||
console.register(
|
||
"weather", "поставить погоду", _cmd_weather,
|
||
"clear|cloudy|overcast|rain|storm|fog|snow"
|
||
)
|
||
console.register("temp", "задать температуру", _cmd_temp, "°C")
|
||
console.register("wind", "задать ветер", _cmd_wind, "м/с")
|
||
console.register("build", "построить комнату", _cmd_build, "тип этаж колонка")
|
||
console.register("wipe", "снести все комнаты", _cmd_wipe)
|
||
|
||
|
||
func _cmd_stats(_arguments: Array[String]) -> String:
|
||
var totals := _grid.totals()
|
||
return "\n".join(PackedStringArray([
|
||
"Время: %s, %s (%s, x%d)" % [
|
||
_clock.date_text(), _clock.time_text(),
|
||
"идёт" if _clock.running else "пауза", _clock.speed,
|
||
],
|
||
"Погода: %s, %s, ветер %s" % [
|
||
_weather.label(),
|
||
Climate.temperature_text(_weather.temperature),
|
||
Climate.wind_text(_weather.wind_speed),
|
||
],
|
||
"Облака: %.2f · осадки %.2f · дымка %.2f" % [
|
||
_weather.clouds, _weather.precipitation, _weather.fog,
|
||
],
|
||
"Комнат: %d" % _grid.rooms.size(),
|
||
"Ресурсы: питание %+d, вода %+d, еда %+d" % [
|
||
int(totals["power"]), int(totals["water"]), int(totals["food"]),
|
||
],
|
||
]))
|
||
|
||
|
||
func _cmd_time(arguments: Array[String]) -> String:
|
||
if arguments.is_empty():
|
||
return "Нужен аргумент: time ЧЧ:ММ"
|
||
var parts := arguments[0].split(":")
|
||
if parts.size() != 2:
|
||
return "Не разобрал время: %s" % arguments[0]
|
||
|
||
var moment := _clock.now()
|
||
moment["hour"] = clampi(int(String(parts[0])), 0, 23)
|
||
moment["minute"] = clampi(int(String(parts[1])), 0, 59)
|
||
if not _clock.set_moment(moment):
|
||
return "Этот момент раньше начала партии."
|
||
|
||
_refresh_all()
|
||
return "Часы: %s" % _clock.time_text()
|
||
|
||
|
||
func _cmd_date(arguments: Array[String]) -> String:
|
||
if arguments.is_empty():
|
||
return "Нужен аргумент: date ДД.ММ.ГГГГ"
|
||
var parts := arguments[0].split(".")
|
||
if parts.size() != 3:
|
||
return "Не разобрал дату: %s" % arguments[0]
|
||
|
||
var moment := _clock.now()
|
||
moment["day"] = clampi(int(String(parts[0])), 1, 31)
|
||
moment["month"] = clampi(int(String(parts[1])), 1, 12)
|
||
moment["year"] = int(String(parts[2]))
|
||
if not _clock.set_moment(moment):
|
||
return "Этот момент раньше начала партии."
|
||
|
||
_refresh_all()
|
||
return "Дата: %s" % _clock.date_text()
|
||
|
||
|
||
func _cmd_speed(arguments: Array[String]) -> String:
|
||
if arguments.is_empty():
|
||
return "Нужен аргумент: speed 0..4"
|
||
var value := int(arguments[0])
|
||
if value <= 0:
|
||
_clock.pause()
|
||
return "Пауза."
|
||
_clock.set_speed(value)
|
||
return "Скорость x%d" % _clock.speed
|
||
|
||
|
||
func _cmd_weather(arguments: Array[String]) -> String:
|
||
if arguments.is_empty():
|
||
return "Нужен аргумент: weather clear|cloudy|overcast|rain|storm|fog|snow"
|
||
|
||
# Понимаем и латинский ключ, и русское название — как удобнее.
|
||
var wanted := arguments[0].to_lower()
|
||
for kind: int in WeatherSystem.KINDS:
|
||
var entry: Dictionary = WeatherSystem.KINDS[kind]
|
||
if String(entry["key"]) != wanted and String(entry["label"]).to_lower() != wanted:
|
||
continue
|
||
_weather.force_kind(kind)
|
||
_refresh_all()
|
||
return "Погода: %s" % _weather.label()
|
||
return "Не знаю такой погоды: %s" % arguments[0]
|
||
|
||
|
||
func _cmd_temp(arguments: Array[String]) -> String:
|
||
if arguments.is_empty():
|
||
return "Нужен аргумент: temp °C"
|
||
_weather.nudge_temperature(float(arguments[0]))
|
||
_refresh_all()
|
||
return "Температура: %s" % Climate.temperature_text(_weather.temperature)
|
||
|
||
|
||
func _cmd_wind(arguments: Array[String]) -> String:
|
||
if arguments.is_empty():
|
||
return "Нужен аргумент: wind м/с"
|
||
_weather.set_wind_speed(float(arguments[0]))
|
||
_refresh_all()
|
||
return "Ветер: %s" % Climate.wind_text(_weather.wind_speed)
|
||
|
||
|
||
func _cmd_build(arguments: Array[String]) -> String:
|
||
if arguments.size() < 3:
|
||
return "Нужно: build тип этаж колонка. Типы: %s" % ", ".join(VaultGrid.KINDS.keys())
|
||
|
||
var kind := arguments[0]
|
||
if not VaultGrid.KINDS.has(kind):
|
||
return "Нет такого типа: %s" % kind
|
||
|
||
var a_floor := int(arguments[1]) - 1
|
||
var column := int(arguments[2])
|
||
if not _grid.place(kind, a_floor, column):
|
||
return "Сюда не поставить: занято или вплотную к шахте."
|
||
|
||
_after_change("Построено из консоли: %s" % VaultGrid.KINDS[kind]["label"])
|
||
return "Готово: %s на этаже %d" % [VaultGrid.KINDS[kind]["label"], a_floor + 1]
|
||
|
||
|
||
func _cmd_wipe(_arguments: Array[String]) -> String:
|
||
var removed := _grid.rooms.size()
|
||
_grid.rooms.clear()
|
||
_after_change("Убежище очищено.")
|
||
return "Снесено комнат: %d" % removed
|
||
|
||
|
||
## После вмешательства извне проще обновить всё, чем вспоминать, что зависит
|
||
## от чего.
|
||
func _refresh_all() -> void:
|
||
_refresh_moment()
|
||
_refresh_environment()
|
||
_refresh_weather_hud()
|
||
_refresh_flow()
|
||
_refresh_stats()
|
||
|
||
|
||
# --- Ход времени ------------------------------------------------------------
|
||
|
||
func _build_flow_controls() -> void:
|
||
# Тема рассчитана на крупные кнопки меню; для панели времени нужен свой,
|
||
# компактный набор стилей. Ресурсы общие на все кнопки — их всего четыре.
|
||
var styles := {
|
||
"normal": _flow_style("#ffffff0d", "#ffffff1f"),
|
||
"hover": _flow_style("#2ee6c51f", "#2ee6c566"),
|
||
"pressed": _flow_style("#2ee6c52e", "#2ee6c5cc"),
|
||
"focus": _flow_style("#2ee6c51a", "#2ee6c599"),
|
||
}
|
||
|
||
_play_button = _add_flow_button("▶", _flow_group, styles)
|
||
_play_button.pressed.connect(_clock.play)
|
||
|
||
_pause_button = _add_flow_button("❚❚", _flow_group, styles)
|
||
_pause_button.pressed.connect(_clock.pause)
|
||
|
||
var gap := Control.new()
|
||
gap.custom_minimum_size.x = 8
|
||
_flow_controls.add_child(gap)
|
||
|
||
for multiplier: int in GameClock.SPEEDS:
|
||
# Выбор скорости заодно снимает паузу — отдельно жать «play» не нужно.
|
||
var button := _add_flow_button("x%d" % multiplier, _speed_group, styles)
|
||
button.pressed.connect(_clock.set_speed.bind(multiplier))
|
||
_speed_buttons[multiplier] = button
|
||
|
||
|
||
func _add_flow_button(label: String, group: ButtonGroup, styles: Dictionary) -> Button:
|
||
var button := Button.new()
|
||
button.text = label
|
||
button.toggle_mode = true
|
||
button.button_group = group
|
||
button.custom_minimum_size.x = 34
|
||
button.add_theme_font_size_override("font_size", 13)
|
||
for state: String in styles:
|
||
button.add_theme_stylebox_override(state, styles[state])
|
||
_flow_controls.add_child(button)
|
||
return button
|
||
|
||
|
||
func _flow_style(background: String, border: String) -> StyleBoxFlat:
|
||
var style := StyleBoxFlat.new()
|
||
style.bg_color = Color(background)
|
||
style.border_color = Color(border)
|
||
style.set_border_width_all(1)
|
||
style.set_corner_radius_all(5)
|
||
style.content_margin_left = 9.0
|
||
style.content_margin_right = 9.0
|
||
style.content_margin_top = 4.0
|
||
style.content_margin_bottom = 4.0
|
||
return style
|
||
|
||
|
||
func _on_minute_passed(_total_minutes: int) -> void:
|
||
_refresh_moment()
|
||
_refresh_environment()
|
||
_refresh_weather_hud()
|
||
|
||
|
||
func _on_weather_changed(_kind: int) -> void:
|
||
_refresh_weather_hud()
|
||
|
||
|
||
func _refresh_weather_hud() -> void:
|
||
var hour := SkyCycle.hour_of(_clock.now())
|
||
var is_night := SkyCycle.sun_progress(hour) < 0.0
|
||
|
||
_weather_icon.set_state(_weather.kind, is_night)
|
||
_weather_label.text = _weather.label()
|
||
_temp_label.text = Climate.temperature_text(_weather.temperature)
|
||
_wind_label.text = Climate.wind_text(_weather.wind_speed)
|
||
|
||
|
||
func _refresh_moment() -> void:
|
||
_date_label.text = _clock.date_text()
|
||
_time_label.text = _clock.time_text()
|
||
|
||
|
||
## Небо и поверхность зависят от одних и тех же двух вещей — часа и погоды.
|
||
func _refresh_environment() -> void:
|
||
var hour := SkyCycle.hour_of(_clock.now())
|
||
_sky.set_conditions(hour, _weather.clouds)
|
||
_surface.set_conditions(hour, _weather)
|
||
|
||
|
||
## Кнопки только отражают состояние часов — источник правды там.
|
||
func _refresh_flow() -> void:
|
||
# Облака и осадки идут в такт игре: на паузе они замирают.
|
||
_surface.time_scale = float(_clock.speed) if _clock.running else 0.0
|
||
_play_button.set_pressed_no_signal(_clock.running)
|
||
_pause_button.set_pressed_no_signal(not _clock.running)
|
||
for multiplier: int in _speed_buttons:
|
||
var button: Button = _speed_buttons[multiplier]
|
||
button.set_pressed_no_signal(multiplier == _clock.speed)
|
||
|
||
|
||
# --- 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:
|
||
# Отдельной кнопки «Выбор» нет: выбор — это состояние по умолчанию, когда
|
||
# ни один инструмент не нажат. allow_unpress даёт отжать активный
|
||
# инструмент повторным кликом и вернуться к выбору.
|
||
_tool_group.allow_unpress = true
|
||
|
||
for kind: String in VaultGrid.KINDS:
|
||
var button := Button.new()
|
||
button.text = String(VaultGrid.KINDS[kind]["label"])
|
||
button.toggle_mode = true
|
||
button.button_group = _tool_group
|
||
button.add_theme_font_size_override("font_size", 18)
|
||
button.toggled.connect(_on_kind_toggled.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 = _tool_group
|
||
demolish.add_theme_font_size_override("font_size", 18)
|
||
demolish.toggled.connect(_on_demolish_toggled)
|
||
_tools.add_child(demolish)
|
||
|
||
|
||
## Возврат к выбору: ни один инструмент не нажат.
|
||
func _reset_to_select() -> void:
|
||
if _mode == Mode.SELECT:
|
||
return
|
||
_mode = Mode.SELECT
|
||
_build_kind = ""
|
||
|
||
var active := _tool_group.get_pressed_button()
|
||
if active != null:
|
||
active.set_pressed_no_signal(false)
|
||
|
||
_status.text = HINT
|
||
|
||
|
||
func _on_kind_toggled(is_on: bool, kind: String) -> void:
|
||
if not is_on:
|
||
# Инструмент отжали — если он и был активным, возвращаемся к выбору.
|
||
if _mode == Mode.BUILD and _build_kind == kind:
|
||
_reset_to_select()
|
||
return
|
||
|
||
_mode = Mode.BUILD
|
||
_build_kind = kind
|
||
_set_selection(Vector2i(-1, -1))
|
||
_status.text = "Стройка: %s · ЛКМ — поставить · ПКМ — отмена" % VaultGrid.KINDS[kind]["label"]
|
||
|
||
|
||
func _on_demolish_toggled(is_on: bool) -> void:
|
||
if not is_on:
|
||
if _mode == Mode.DEMOLISH:
|
||
_reset_to_select()
|
||
return
|
||
|
||
_mode = Mode.DEMOLISH
|
||
_build_kind = ""
|
||
_set_selection(Vector2i(-1, -1))
|
||
_status.text = "Снос: кликните по комнате · ПКМ — отмена"
|
||
|
||
|
||
# --- Сохранение -------------------------------------------------------------
|
||
|
||
func save_data() -> Dictionary:
|
||
return {
|
||
"rooms": _grid.to_array(),
|
||
"clock": _clock.save_data(),
|
||
"weather": _weather.save_data(),
|
||
"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", []))
|
||
_clock.load_data(data.get("clock", {}))
|
||
_weather.load_data(data.get("weather", {}))
|
||
_refresh_environment()
|
||
|
||
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)
|
||
|
||
_set_selection(Vector2i(-1, -1))
|
||
_vault.queue_redraw()
|
||
_refresh_stats()
|
||
_status.text = "Убежище загружено."
|