farming-simulator/scripts/prologue/act2_install.gd
Dmitrii Bykov 79b19a488a
Some checks failed
Godot CI / Import + smoke test (push) Failing after 13s
fix: ПК-логика канон, лицензия 30 пунктов, footprint расчёты
ПК НА СТОЛЕ:
- Раньше: OldPC (монитор) был скрыт через _hide_noise_items
  вместе с OldP4 (старый системник) → игрок видел только пустой
  стол с подарком на полу.
- Теперь:
  * OldPC (монитор) — ВИДИМ на столе слева (был ошибочно в hide-list)
  * OldP4 (старый системник Pentium 4) — ВИДИМ в углу (был в hide)
  * SystemUnit (новый системник из подарка) — появляется НА СТОЛЕ
    рядом с монитором, не на полу.

ПОЗИЦИИ — ПРОСЧИТАНО:
- Desk world: x=[-0.75..0.75], y=[0..0.7], z=[-1.8..-1.2]
- OldPC (монитор) world: x=[-0.625..-0.375], y=[0.7..1.1], z=[-1.75..-1.45]
- SystemUnit новая позиция: x=[-0.375..-0.025], y=[0.7..1.2], z=[-1.725..-1.275]
  → ВПЛОТНУЮ к монитору на столе. X-диапазоны касаются на -0.375
  но не пересекаются.
- FlashDrive world: x=[0.37..0.43], z=[-1.4..-1.35]
  → ИЗОЛИРОВАН от SystemUnit (раньше был внутри её footprint —
  системник бы накрыл флешку).

АНИМАЦИЯ ПОДАРКА:
- Раньше: gift_box просто пропадал (gift_box.visible=false).
- Теперь: tween scale → Vector3.ZERO за 0.3 сек → выглядит как
  «коробка съёжилась и пропала», игрок видит процесс.

ДИАЛОГИ С КАНОН-ЛОГИКОЙ:
- Раньше после открытия: «Системник. И записка сверху.»
- Теперь:
  1. «Ого. Реально новый системник.» — реакция
  2. «Поставлю на стол — монитор-то старый остаётся, подключим.» — логика
  3. «А это записка на нём…» — направление взгляда
  → объясняет почему системник на столе, а не на полу.

ЛИЦЕНЗИЯ — 30 ПУНКТОВ:
- Было: 1.1-6.8 (нормальные) + 7 ШИЗО = 14 пунктов.
- Стало: 30 пунктов от 1.1 до 28.0 с реальными юридическими
  формулировками между ШИЗО — про неисключительные права,
  дизассемблирование, гарантии «AS IS», подсудность, изменение
  условий в одностороннем порядке, использование вычислительных
  мощностей пользователя, ну и в конце «Спасибо за внимание.»
- Реально читается как Microsoft EULA.

TASK #35 СОЗДАН: полная 2-комн квартира (прихожая + кухня + зал +
детская + ванная + туалет) — это большой архитектурный реворк,
сделаю отдельным коммитом.

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

311 lines
20 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]п. 1.1[/i] Настоящее Лицензионное соглашение (далее — Соглашение) является обязывающим юридическим документом между Вами (далее — Пользователь) и корпорацией Шиндовс-Корп (далее — Корпорация).\n\n[i]п. 1.2[/i] Установив, скопировав, загрузив или иным образом используя ПО Шиндовс 9, Пользователь подтверждает согласие с условиями настоящего Соглашения в полном объёме.\n\n[i]п. 1.3[/i] Соглашение вступает в силу с момента нажатия кнопки «Принять» и действует бессрочно, либо до момента расторжения по инициативе Корпорации.\n\n[i]п. 2.1[/i] Корпорация предоставляет Пользователю неисключительную, непередаваемую, отзываемую лицензию на использование ПО на одном персональном устройстве, принадлежащем Пользователю на законных основаниях.\n\n[i]п. 2.4[/i] Пользователь не имеет права передавать, сдавать в аренду, продавать или иным образом распространять ПО третьим лицам без письменного согласия Корпорации.\n\n[i]п. 3.4[/i] Запрещается дизассемблировать, декомпилировать, реверс-инжинирить или иным образом изучать внутреннюю работу ПО, кроме случаев, прямо разрешённых императивными нормами законодательства.\n\n[i]п. 3.7[/i] Пользователь обязуется не использовать ПО для разработки конкурирующих программных продуктов в течение 99 лет с момента установки.\n\n[i]п. 4.2[/i] Корпорация оставляет за собой право обновлять, модифицировать или прекращать поддержку ПО в любое время без предварительного уведомления Пользователя.\n\n[i]п. 4.5[/i] В случае обновления Пользователь автоматически соглашается с любыми изменёнными условиями Соглашения, даже если такие изменения существенно ухудшают права Пользователя.\n\n[i]п. 5.1[/i] ПО предоставляется «как есть» («AS IS»). Корпорация не предоставляет никаких гарантий, ни явных, ни подразумеваемых, включая, но не ограничиваясь, гарантиями пригодности для конкретной цели.\n\n[i]п. 5.3[/i] Корпорация не несёт ответственности за любые прямые, косвенные, случайные, специальные или сопутствующие убытки, включая упущенную выгоду, потерю данных или деловой репутации.\n\n[i]п. 6.1[/i] Пользователь обязуется не использовать ПО для деятельности, противоречащей действующему законодательству страны проживания Пользователя, либо страны нахождения серверов Корпорации.\n\n[i]п. 6.8[/i] В случае нарушения условий Соглашения Корпорация вправе расторгнуть лицензию в одностороннем порядке без возврата уплаченных средств.\n\n[i]п. 7.3.2[/i] Этот персональный компьютер, на который Вы устанавливаете ПО, формально остаётся собственностью корпорации Шиндовс-Корп до момента окончательной выплаты Вашей души (далее — Душа).\n\n[i]п. 8.2[/i] Пользователь предоставляет Корпорации неисключительное право собирать, обрабатывать и хранить телеметрические данные о работе ПО на устройстве Пользователя.\n\n[i]п. 9.4[/i] Корпорация не гарантирует совместимость ПО с оборудованием, выпущенным позднее даты выпуска ПО, либо ранее даты выпуска предыдущей версии ПО.\n\n[i]п. 10.1[/i] Пользователь обязуется не предъявлять Корпорации никаких претензий за неудобства, вызванные обязательной перезагрузкой устройства в произвольное время.\n\n[i]п. 11.4[/i] Корпорация оставляет за собой право в любой момент запустить процесс «svchost.exe» с приоритетом «realtime». Жалобы не принимаются.\n\n[i]п. 12.1[/i] Все ваши скриншоты могут быть использованы в обучении ИИ, который заменит Вас на работе к 2027 году. Согласие подразумевается.\n\n[i]п. 13.6[/i] Корпорация имеет право демонстрировать рекламу третьих лиц в любой части интерфейса ПО, включая, но не ограничиваясь, экраном блокировки, меню «Пуск», уведомлениями и обновлениями.\n\n[i]п. 14.2[/i] В случае конфликта между настоящим Соглашением и любыми устными договорённостями преимущественную силу имеет настоящее Соглашение.\n\n[i]п. 15.7[/i] Корпорация молча переустановит Защитник раз в сутки, если установлен сторонний антивирус.\n\n[i]п. 16.9[/i] Пользователь признаёт, что любая попытка отключить телеметрию является нарушением Соглашения и может привести к блокировке учётной записи.\n\n[i]п. 18.0[/i] Раз в год Ваш рабочий стол может быть перемещён в облако. Восстановление платное.\n\n[i]п. 19.3[/i] Корпорация имеет право использовать вычислительные мощности устройства Пользователя для нужд распределённых вычислений в фоновом режиме.\n\n[i]п. 21.3[/i] Корпорация не несёт ответственности за случаи, когда после обновления у Вас перестаёт работать звук, мышь или Wi-Fi. Это нормально. Перезагрузитесь.\n\n[i]п. 22.5[/i] Споры, возникающие из настоящего Соглашения, подлежат разрешению в Арбитражном суде юрисдикции, выбранной Корпорацией по её усмотрению.\n\n[i]п. 23.9[/i] Все приложения могут наблюдать друг за другом и отправлять телеметрию в Облако. Облако — это не место, а состояние ума.\n\n[i]п. 25.1[/i] Корпорация оставляет за собой право в одностороннем порядке изменять, дополнять или удалять любые положения настоящего Соглашения без предварительного уведомления Пользователя.\n\n[i]п. 26.4[/i] Если какое-либо положение настоящего Соглашения признано недействительным, остальные положения сохраняют полную силу. Недействительное положение заменяется аналогичным действительным.\n\n[i]п. 27.1[/i] Нажав «Принять», Вы соглашаетесь не читать это соглашение. Если Вы дочитали до этого пункта — Вы нарушили условия. Закройте окно и попробуйте снова.\n\n[i]п. 28.0[/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()