farming-simulator/scripts/prologue/act2_install.gd
Dmitrii Bykov 6db9c4947b
Some checks failed
Godot CI / Import + smoke test (push) Failing after 14s
fix Акт 2: позиционирование Body адаптивное (compact vs scrollable)
Проблема: Body RichTextLabel был с custom_minimum_size (540, 220)
ВСЕГДА. На шагах с коротким текстом (диск/имя/пароль/часовой пояс)
лейбл занимал 220px пустого пространства между заголовком и
полями ввода, выдавливая чекбокс/кнопки за пределы экрана.

Решение:
- В .tscn Body custom_minimum_size = (540, 0), fit_content=true
  (адаптируется под контент).
- Window custom_minimum_size = (580, 220) — раньше 348, теперь
  окно само растёт под content.
- В скрипте 2 хелпера:
  * _set_body_compact() — fit_content=true, min_size (540, 0).
    Используется для всех коротких шагов.
  * _set_body_scrollable(h) — fit_content=false, min_size (540, h),
    scroll_to_line(0). Используется только для LICENSE_FULL.
- Все шаги (_to_disk, _to_username, _to_password, _to_timezone,
  _to_installing) вызывают _set_body_compact() в начале.
- LICENSE_FULL вызывает _set_body_scrollable(220) для скроллябельной
  лицензии + в title подсказка «прокрути колесом мыши».

Теперь окно установки правильно ужимается под короткий контент и
все поля видны на экране.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 19:21:43 +03:00

311 lines
13 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

extends Control
@onready var title_label: Label = $Center/Window/V/TitleBar/Title
@onready var body_label: RichTextLabel = $Center/Window/V/Body
@onready var input_field: LineEdit = $Center/Window/V/Input
@onready var input_hint: Label = $Center/Window/V/InputHint
@onready var btn_a: Button = $Center/Window/V/Buttons/BtnA
@onready var btn_b: Button = $Center/Window/V/Buttons/BtnB
@onready var error_label: Label = $Center/Window/V/ErrorLabel
@onready var checkbox_no_pwd: CheckBox = $Center/Window/V/CheckBoxNoPwd
@onready var tz_dropdown: OptionButton = $Center/Window/V/TimeZone
@onready var progress_bar: ProgressBar = $Center/Window/V/Progress
@onready var four_squares: Control = $FourSquares
@onready var welcome_screen: Control = $WelcomeScreen
@onready var welcome_text: Label = $WelcomeScreen/V/WelcomeText
enum Step { BOOT, LICENSE_INTRO, LICENSE_FULL, DISK, USERNAME, PASSWORD, TIMEZONE, INSTALLING, COMPLETE }
var current_step: Step = Step.BOOT
var license_read_clicked: bool = false
const BANNED_TITLES: Array[String] = ["повелитель", "владыка", "бог", "лорд", "царь", "хозяин"]
const BANNED_MAT: Array[String] = ["хуй", "пизд", "ебал", "ебан", "бля", "сук", "блять"]
const USERNAME_PLACEHOLDERS: Array[String] = [
"bossiara13",
"fr3sh_n3kit",
"Vasyok_07",
"neoLich",
"Димаа_рулём",
"p1ano",
]
const TIMEZONES: Array[String] = [
"UTC+2 (Калининград)",
"UTC+3 (С., страна Р)",
"UTC+4 (Самара)",
"UTC+5 (Екатеринбург)",
"UTC+7 (Новосибирск)",
"UTC+9 (Иркутск)",
"UTC+10 (Владивосток)",
]
const LICENSE_SHORT: String = "[center][b]Лицензионное соглашение Шиндовс 9[/b][/center]\n\nНажимая «Принять», Вы соглашаетесь с условиями использования операционной системы Шиндовс-Корп.\n\nДля просмотра полного текста нажмите «Читать»."
const LICENSE_FULL: String = "[center][b]Лицензионное соглашение Шиндовс 9 (полная версия)[/b][/center]\n\n[i]п. 7.3.2[/i] Этот персональный компьютер, на который Вы устанавливаете ПО, формально остаётся собственностью корпорации Шиндовс-Корп до момента окончательной выплаты Вашей души (далее — Душа).\n\n[i]п. 11.4[/i] Корпорация оставляет за собой право в любой момент запустить процесс «svchost.exe» с приоритетом «realtime». Жалобы не принимаются.\n\n[i]п. 12.1[/i] Все ваши скриншоты могут быть использованы в обучении ИИ, который заменит Вас на работе к 2027 году. Согласие подразумевается.\n\n[i]п. 15.7[/i] Корпорация молча переустановит Защитник раз в сутки, если установлен сторонний антивирус.\n\n[i]п. 18.0[/i] Раз в год Ваш рабочий стол может быть перемещён в облако. Восстановление платное.\n\n[i]п. 21.3[/i] Корпорация не несёт ответственности за случаи, когда после обновления у Вас перестаёт работать звук, мышь или Wi-Fi. Это нормально. Перезагрузитесь.\n\n[i]п. 23.9[/i] Все приложения могут наблюдать друг за другом и отправлять телеметрию в Облако. Облако — это не место, а состояние ума.\n\n[i]п. 27.1[/i] Нажав «Принять», Вы соглашаетесь не читать это соглашение. Если Вы дочитали до этого пункта — Вы нарушили условия."
func _ready() -> void:
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
DialogManager.clear_prompt()
DialogManager.set_controls_hint("")
DialogManager.set_crosshair_visible(false)
DialogManager.set_date_time(GameState.game_date, GameState.game_time)
DialogManager.set_objective("Установи Шиндовс 9")
welcome_screen.visible = false
progress_bar.visible = false
input_field.visible = false
input_hint.visible = false
checkbox_no_pwd.visible = false
tz_dropdown.visible = false
error_label.visible = false
btn_a.pressed.connect(_on_btn_a)
btn_b.pressed.connect(_on_btn_b)
for tz in TIMEZONES:
tz_dropdown.add_item(tz)
tz_dropdown.select(1) # UTC+3 по умолчанию
# BOOT: 4 квадратика на 1.5 сек
four_squares.visible = true
await get_tree().create_timer(1.6).timeout
four_squares.visible = false
_to_license_intro()
func _set_body_compact() -> void:
body_label.fit_content = true
body_label.custom_minimum_size = Vector2(540, 0)
func _set_body_scrollable(min_h: int) -> void:
body_label.fit_content = false
body_label.custom_minimum_size = Vector2(540, min_h)
body_label.scroll_to_line(0)
func _to_license_intro() -> void:
current_step = Step.LICENSE_INTRO
title_label.text = "Шиндовс 9 — Шаг 1 из 5"
_set_body_compact()
body_label.text = LICENSE_SHORT
body_label.visible = true
btn_a.text = "Принять"
btn_a.visible = true
btn_b.text = "Читать"
btn_b.visible = true
input_field.visible = false
input_hint.visible = false
checkbox_no_pwd.visible = false
tz_dropdown.visible = false
error_label.visible = false
func _to_license_full() -> void:
current_step = Step.LICENSE_FULL
license_read_clicked = true
title_label.text = "Шиндовс 9 — Полный текст соглашения (прокрути колесом мыши)"
_set_body_scrollable(220)
body_label.text = LICENSE_FULL
btn_a.text = "Принять"
btn_a.visible = true
btn_b.visible = false
func _to_disk() -> void:
current_step = Step.DISK
title_label.text = "Шиндовс 9 — Шаг 2 из 5"
_set_body_compact()
body_label.text = "[center][b]Выбор диска для установки[/b][/center]\n\n[•] Диск C:\\\\ (250 ГБ свободно)\n\nДругие диски не обнаружены."
btn_a.text = "Далее"
btn_a.visible = true
btn_b.visible = false
func _to_username() -> void:
current_step = Step.USERNAME
title_label.text = "Шиндовс 9 — Шаг 3 из 5"
_set_body_compact()
body_label.text = "[center][b]Введите имя пользователя[/b][/center]\n\nЭто имя будет использоваться в системе. Минимум 2 символа, максимум 24. Только буквы и цифры."
var rng: RandomNumberGenerator = RandomNumberGenerator.new()
rng.randomize()
input_field.placeholder_text = USERNAME_PLACEHOLDERS[rng.randi() % USERNAME_PLACEHOLDERS.size()]
input_field.text = ""
input_field.visible = true
input_field.editable = true
input_field.grab_focus()
input_hint.text = "Например: bossiara13, fr3sh_n3kit, Vasyok_07, neoLich, КошкоПовелитель… ой, нет, этот нельзя"
input_hint.visible = true
error_label.visible = false
btn_a.text = "Далее"
btn_a.visible = true
btn_b.visible = false
func _to_password() -> void:
current_step = Step.PASSWORD
title_label.text = "Шиндовс 9 — Шаг 4 из 5"
_set_body_compact()
body_label.text = "[center][b]Введите пароль[/b][/center]\n\nИли оставьте поле пустым / отметьте «Без пароля». Можно потом изменить в настройках."
input_field.text = ""
input_field.secret = true
input_field.placeholder_text = "********"
input_field.visible = true
input_field.editable = true
input_field.grab_focus()
input_hint.text = "Совет: запиши себе на бумажку. Минимум 4 символа, если ставишь."
input_hint.visible = true
checkbox_no_pwd.visible = true
checkbox_no_pwd.button_pressed = false
error_label.visible = false
btn_a.text = "Далее"
btn_a.visible = true
btn_b.visible = false
func _to_timezone() -> void:
current_step = Step.TIMEZONE
title_label.text = "Шиндовс 9 — Шаг 5 из 5"
_set_body_compact()
body_label.text = "[center][b]Выберите часовой пояс[/b][/center]\n\nЭто повлияет на время в системе. По умолчанию — UTC+3 (С., страна Р)."
input_field.visible = false
input_field.secret = false
input_hint.visible = false
checkbox_no_pwd.visible = false
tz_dropdown.visible = true
btn_a.text = "Готово"
btn_a.visible = true
btn_b.visible = false
func _to_installing() -> void:
current_step = Step.INSTALLING
title_label.text = "Шиндовс 9 — Установка"
_set_body_compact()
body_label.text = "[center]Подождите, пока система устанавливается.\nЭто займёт пару минут.[/center]"
input_field.visible = false
input_hint.visible = false
checkbox_no_pwd.visible = false
tz_dropdown.visible = false
btn_a.visible = false
btn_b.visible = false
progress_bar.visible = true
progress_bar.value = 0
_run_install_progress()
func _run_install_progress() -> void:
var stages: Array = [
["Распаковка файлов…", 0.25],
["Установка драйверов…", 0.5],
["Настройка реестра…", 0.75],
["Применение конфигурации…", 0.95],
["Готово!", 1.0],
]
for stage in stages:
body_label.text = "[center]" + stage[0] + "[/center]"
var tween: Tween = create_tween()
tween.tween_property(progress_bar, "value", stage[1] * 100.0, 0.6)
await tween.finished
await get_tree().create_timer(0.4).timeout
_to_complete()
func _to_complete() -> void:
current_step = Step.COMPLETE
$Center.visible = false
welcome_screen.visible = true
welcome_text.text = "Добро пожаловать, %s." % GameState.player_name
DialogManager.set_objective("")
func _on_btn_a() -> void:
match current_step:
Step.LICENSE_INTRO:
if license_read_clicked:
GameState.unlock_achievement("Юрист")
GameState.unlock_achievement("Нарушитель ЛС")
_to_disk()
Step.LICENSE_FULL:
GameState.unlock_achievement("Юрист")
GameState.unlock_achievement("Нарушитель ЛС")
_to_disk()
Step.DISK:
_to_username()
Step.USERNAME:
_validate_and_set_username()
Step.PASSWORD:
_validate_and_set_password()
Step.TIMEZONE:
GameState.act2_timezone = TIMEZONES[tz_dropdown.selected]
_to_installing()
func _on_btn_b() -> void:
match current_step:
Step.LICENSE_INTRO:
_to_license_full()
func _validate_and_set_username() -> void:
var name: String = input_field.text.strip_edges()
var result: Dictionary = _validate_name(name)
if not result.get("valid", false):
error_label.text = result.get("error", "Ошибка")
error_label.visible = true
return
GameState.player_name = name
_to_password()
func _validate_name(name: String) -> Dictionary:
if name.length() < 2:
return {"valid": false, "error": "Минимум 2 символа"}
if name.length() > 24:
return {"valid": false, "error": "Максимум 24 символа"}
var re: RegEx = RegEx.new()
re.compile("^[\\wа-яёА-ЯЁ_]+$")
if not re.search(name):
return {"valid": false, "error": "Только буквы, цифры и подчёркивания"}
var lower: String = name.to_lower()
for t in BANNED_TITLES:
if t in lower:
return {"valid": false, "error": "Без громких титулов, ок?"}
for m in BANNED_MAT:
if m in lower:
return {"valid": false, "error": "Эээ, давай прилично"}
return {"valid": true}
func _validate_and_set_password() -> void:
if checkbox_no_pwd.button_pressed:
GameState.act2_password_set = false
GameState.unlock_achievement("Главное чтоб удобно")
_to_timezone()
return
var pwd: String = input_field.text
if pwd.is_empty() or pwd in ["123", "qwerty", "password", "1234"]:
GameState.act2_password_set = false
GameState.unlock_achievement("Главное чтоб удобно")
_to_timezone()
return
if pwd.length() >= 12 and _has_special(pwd):
GameState.unlock_achievement("Параноик")
GameState.act2_password_set = true
_to_timezone()
func _has_special(s: String) -> bool:
var specials: String = "!@#$%^&*()-_+=<>?/.,"
for c in s:
if c in specials:
return true
return false
func _on_launch_steme() -> void:
# Акт 3 пока не реализован — выходим
GameState.unlock_achievement("Конец альфа-сессии")
await DialogManager.say("СИСТЕМА", "Акт 3 (стеме Big Picture) — TBD в следующей итерации. Игрок: %s. Спасибо за тест!" % GameState.player_name)
get_tree().quit()