Some checks failed
Godot CI / Import + smoke test (push) Failing after 13s
1. ОТЕЦ (поворот по фидбеку):
- Initial Transform3D повёрнут на 180° от предыдущего — теперь
лицом к игроку во время диалога.
- При уходе ещё +180° поворот → спиной к игроку → шагает к двери
не пятясь.
2. AUTO-FLOW КЛИКА ПО ПОДАРКУ:
- Раньше: клик подарок → молча появляется системник → клик
записку → overlay → клик дальше → монолог → ... Игрок терялся
между кликами, думал что баг (тёмный экран).
- Теперь: ОДИН клик подарка запускает ЦЕПОЧКУ:
«Системник. И записка сверху.» → overlay записки → «Бать ну
ты красавчик» → отец прощается → отец уходит → флешка готова.
Игрок просто кликает «дальше» на каждой реплике.
- note.set_interactable(false) — клик отдельно не нужен.
- _on_note_interacted остался как пустой callback для совместимости.
3. КУХНЯ — СПАВН:
- Был (0.5, 0, 1.0) — попадал внутрь стола (z=0.35..1.05).
Игрок застревал.
- Теперь (-0.5, 0, 1.2) — у двери возврата в комнату, далеко от
стола. Спавнится свободно.
4. КУХНЯ — 2-Я ДВЕРЬ:
- User спросил «куда отец ушёл — нет других дверей». Добавил
визуальную не-интерактивную дверь на северной стене
(Label3D «← прихожая»). Логически отец вышел через коридор.
5. ЛИЦЕНЗИЯ — НАЧАЛО:
- User: «начинается с п.7, а где 1-6». Добавил 7 нормально-
юридических пунктов 1.1-6.8 (про лицензию, неисключительные
права, дизассемблирование, «как есть», ответственность,
обязательства Пользователя) — ПЕРЕД ШИЗО-пунктами.
- Теперь читается как настоящее ЛС: нормальное-нормальное-
нормальное-ШИЗО-ШИЗО-ШИЗО, нарастающий абсурд.
6. HUD-ПОДСКАЗКА АВТО-ФЕЙД:
- Timer в DialogManager (one_shot, 8 сек по умолчанию).
- set_controls_hint() запускает таймер.
- По истечении — tween modulate.a 0.8 → 0.0 за 1.5 сек.
- Подсказка управления больше не висит вечно.
7. ГЛАВНОЕ МЕНЮ — НАЗВАНИЕ:
- Было: «FARMING SIMULATOR / Подросток-фермер CS/TF2/PUBG
кейсов · 2020-2026» (рабочее, пресно).
- Стало: «ДРОП» (32px, жёлтый акцент) / «Лето двадцатого года.»
(12px, серый). Лаконично, поэтично.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
311 lines
15 KiB
GDScript
311 lines
15 KiB
GDScript
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]п. 2.1[/i] Корпорация предоставляет Пользователю неисключительную, непередаваемую, отзываемую лицензию на использование ПО на одном персональном устройстве.\n\n[i]п. 3.4[/i] Запрещается дизассемблировать, декомпилировать или иным образом изучать внутреннюю работу ПО, кроме случаев, прямо разрешённых законодательством.\n\n[i]п. 4.2[/i] Корпорация оставляет за собой право обновлять, модифицировать или прекращать поддержку ПО в любое время без предварительного уведомления.\n\n[i]п. 5.1[/i] ПО предоставляется «как есть». Корпорация не несёт ответственности за любые прямые, косвенные или сопутствующие убытки, возникшие в результате использования ПО.\n\n[i]п. 6.8[/i] Пользователь обязуется не использовать ПО для деятельности, противоречащей законодательству страны проживания Пользователя.\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()
|