Implement weather system and developer console features. Added functionality for setting weather conditions, temperature, and wind speed through a new developer console. Enhanced the vault scene to display current weather, temperature, and wind information, and updated game clock to allow time manipulation. Improved cloud rendering and introduced a new climate management system for seasonal temperature adjustments.
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
extends CanvasLayer
|
||||
## Консоль разработчика: ввод команд поверх игры.
|
||||
##
|
||||
## Автозагрузка, поэтому доступна из любой сцены. Своих знаний об игре у неё
|
||||
## нет — команды регистрируют сцены через register(). Так консоль не тянет за
|
||||
## собой игровую логику, а команды живут рядом с тем, чем управляют.
|
||||
##
|
||||
## Открывается тильдой или F1. Слой обрабатывается всегда, поэтому работает
|
||||
## и на паузе — иначе отлаживать остановленную игру было бы нечем.
|
||||
|
||||
const HISTORY_LIMIT := 40
|
||||
|
||||
## Файловый мост для внешних инструментов (MCP, скрипты, CI).
|
||||
##
|
||||
## Внедрённые события клавиатуры доносят до LineEdit код клавиши, но не символ,
|
||||
## поэтому набрать команду извне нельзя. Мост обходит клавиатуру: во входной
|
||||
## файл кладут строки, консоль их выполняет и удаляет файл, а весь вывод
|
||||
## дублирует в лог. Только в отладочной сборке — в релиз это не попадёт.
|
||||
const BRIDGE_IN := "user://console_in.txt"
|
||||
const BRIDGE_LOG := "user://console_log.txt"
|
||||
const BRIDGE_INTERVAL := 0.25
|
||||
|
||||
const COLOR_ECHO := "#5d7288"
|
||||
const COLOR_INFO := "#dbe7f0"
|
||||
const COLOR_OK := "#2ee6c5"
|
||||
const COLOR_ERROR := "#f2704a"
|
||||
|
||||
@onready var _root: Control = $Root
|
||||
@onready var _output: RichTextLabel = $Root/Panel/Body/Output
|
||||
@onready var _entry: LineEdit = $Root/Panel/Body/InputRow/Input
|
||||
|
||||
## Имя команды -> {description, usage, handler}
|
||||
var _commands := {}
|
||||
var _history: Array[String] = []
|
||||
var _history_index := 0
|
||||
var _log: FileAccess
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
_root.visible = false
|
||||
_entry.text_submitted.connect(_on_submitted)
|
||||
|
||||
_register_builtin()
|
||||
_start_bridge()
|
||||
print_line("Консоль разработчика. `help` — список команд.", COLOR_INFO)
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if not (event is InputEventKey and event.is_pressed() and not event.is_echo()):
|
||||
return
|
||||
|
||||
var key := (event as InputEventKey).keycode
|
||||
if key == KEY_QUOTELEFT or key == KEY_F1:
|
||||
toggle()
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
|
||||
if not _root.visible:
|
||||
return
|
||||
|
||||
# История — стрелками, как в настоящей консоли.
|
||||
match key:
|
||||
KEY_UP:
|
||||
_recall(-1)
|
||||
get_viewport().set_input_as_handled()
|
||||
KEY_DOWN:
|
||||
_recall(1)
|
||||
get_viewport().set_input_as_handled()
|
||||
KEY_ESCAPE:
|
||||
close()
|
||||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
# --- Открытие / закрытие ----------------------------------------------------
|
||||
|
||||
func toggle() -> void:
|
||||
if _root.visible:
|
||||
close()
|
||||
else:
|
||||
open()
|
||||
|
||||
|
||||
func open() -> void:
|
||||
_root.visible = true
|
||||
_entry.clear()
|
||||
_entry.grab_focus()
|
||||
|
||||
|
||||
func close() -> void:
|
||||
_root.visible = false
|
||||
_entry.release_focus()
|
||||
|
||||
|
||||
# --- Реестр команд ----------------------------------------------------------
|
||||
|
||||
## Сцены регистрируют свои команды здесь. `handler` принимает Array[String]
|
||||
## аргументов и возвращает String — что напечатать.
|
||||
func register(command: String, description: String, handler: Callable, usage := "") -> void:
|
||||
_commands[command] = {
|
||||
"description": description,
|
||||
"usage": usage,
|
||||
"handler": handler,
|
||||
}
|
||||
|
||||
|
||||
func unregister(command: String) -> void:
|
||||
_commands.erase(command)
|
||||
|
||||
|
||||
func print_line(text: String, color := COLOR_INFO) -> void:
|
||||
_output.append_text("[color=%s]%s[/color]\n" % [color, text])
|
||||
if _log != null:
|
||||
# Сбрасываем сразу: внешний инструмент читает файл, пока игра жива.
|
||||
_log.store_line(text)
|
||||
_log.flush()
|
||||
|
||||
|
||||
# --- Файловый мост ----------------------------------------------------------
|
||||
|
||||
func _start_bridge() -> void:
|
||||
if not OS.is_debug_build():
|
||||
return
|
||||
|
||||
# Лог перезаписывается на каждый запуск — так в нём только текущая сессия.
|
||||
_log = FileAccess.open(BRIDGE_LOG, FileAccess.WRITE)
|
||||
|
||||
var timer := Timer.new()
|
||||
timer.wait_time = BRIDGE_INTERVAL
|
||||
timer.autostart = true
|
||||
timer.timeout.connect(_poll_bridge)
|
||||
add_child(timer)
|
||||
|
||||
|
||||
func _poll_bridge() -> void:
|
||||
if not FileAccess.file_exists(BRIDGE_IN):
|
||||
return
|
||||
|
||||
var file := FileAccess.open(BRIDGE_IN, FileAccess.READ)
|
||||
if file == null:
|
||||
return
|
||||
var payload := file.get_as_text()
|
||||
file.close()
|
||||
# Удаляем сразу, иначе команды выполнятся повторно на следующем тике.
|
||||
DirAccess.remove_absolute(BRIDGE_IN)
|
||||
|
||||
for line: String in payload.split("\n", false):
|
||||
_on_submitted(line)
|
||||
|
||||
|
||||
# --- Выполнение -------------------------------------------------------------
|
||||
|
||||
func _on_submitted(line: String) -> void:
|
||||
_entry.clear()
|
||||
var trimmed := line.strip_edges()
|
||||
if trimmed.is_empty():
|
||||
return
|
||||
|
||||
_remember(trimmed)
|
||||
print_line("> " + trimmed, COLOR_ECHO)
|
||||
|
||||
var parts := trimmed.split(" ", false)
|
||||
var command := String(parts[0]).to_lower()
|
||||
var arguments: Array[String] = []
|
||||
for index in range(1, parts.size()):
|
||||
arguments.append(String(parts[index]))
|
||||
|
||||
_execute(command, arguments)
|
||||
|
||||
|
||||
func _execute(command: String, arguments: Array[String]) -> void:
|
||||
if not _commands.has(command):
|
||||
print_line("Неизвестная команда: %s. `help` — список." % command, COLOR_ERROR)
|
||||
return
|
||||
|
||||
var entry: Dictionary = _commands[command]
|
||||
var handler: Callable = entry["handler"]
|
||||
# Сцена могла смениться — её команды умирают вместе с ней.
|
||||
if not handler.is_valid():
|
||||
_commands.erase(command)
|
||||
print_line("Команда %s больше недоступна в этой сцене." % command, COLOR_ERROR)
|
||||
return
|
||||
|
||||
var result: Variant = handler.call(arguments)
|
||||
if result is String and not String(result).is_empty():
|
||||
print_line(String(result), COLOR_OK)
|
||||
|
||||
|
||||
# --- История ----------------------------------------------------------------
|
||||
|
||||
func _remember(line: String) -> void:
|
||||
_history.append(line)
|
||||
if _history.size() > HISTORY_LIMIT:
|
||||
_history.remove_at(0)
|
||||
_history_index = _history.size()
|
||||
|
||||
|
||||
func _recall(step: int) -> void:
|
||||
if _history.is_empty():
|
||||
return
|
||||
_history_index = clampi(_history_index + step, 0, _history.size())
|
||||
if _history_index == _history.size():
|
||||
_entry.clear()
|
||||
return
|
||||
_entry.text = _history[_history_index]
|
||||
_entry.caret_column = _entry.text.length()
|
||||
|
||||
|
||||
# --- Встроенные команды -----------------------------------------------------
|
||||
|
||||
func _register_builtin() -> void:
|
||||
register("help", "список команд", _cmd_help)
|
||||
register("cls", "очистить вывод", _cmd_clear)
|
||||
|
||||
|
||||
func _cmd_help(_arguments: Array[String]) -> String:
|
||||
var names := _commands.keys()
|
||||
names.sort()
|
||||
|
||||
var lines := PackedStringArray()
|
||||
for name: String in names:
|
||||
var entry: Dictionary = _commands[name]
|
||||
var usage := String(entry["usage"])
|
||||
var head := name if usage.is_empty() else "%s %s" % [name, usage]
|
||||
lines.append(" %-24s %s" % [head, String(entry["description"])])
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
func _cmd_clear(_arguments: Array[String]) -> String:
|
||||
_output.clear()
|
||||
return ""
|
||||
Reference in New Issue
Block a user