Some checks failed
Godot CI / Import + smoke test (push) Failing after 14s
БАГ-ФИКСЫ: - Трясёт у двери: Interactable теперь extends Area3D (не StaticBody3D). RayCast3D детектит Area3D через collide_with_areas=true. Игрок физически проходит через интерактивы (gift box, флешка, записка, дверь) — не толкается в узких местах. Бампается только в стены/мебель/пол. - Все 6 интерактивов в act1_room.tscn и act1_kitchen.tscn переведены на type="Area3D". UI: - Crosshair в центре экрана: 8×8 точка с тёмной рамкой и белой центральной 2×2 точкой. set_crosshair_visible() для 2D-сцен (Акт 2 прячет его). - SubtitlePanel расширен: 16-624 × 240-354 (было 24-616 × 270-350). Text-лейбл заменён на RichTextLabel с fit_content + bbcode. Длинные реплики отца теперь нормально влезают. - NoteOverlay аналогично RichTextLabel. - ControlsHint перенесён сверху (под HUD-датой), 14px меньше. ТЕКСТУРЫ через FastNoiseLite + 128×128 разрешалка: - wallpaper: шумная бежевая база + растительный крест-цветок каждые 22px, шахматное смещение, тонкая горизонтальная полоса каждые 32px (стык обоев). - linoleum: настоящая паркетная доска 16×64px со смещением между рядами, тонкие швы, волокно через шум. - ceiling: побелка с пятном протёчки в углу (canon-деталь). - wall_carpet: бордовая база + двойная золотая рамка + ромб- медальон с золотыми углами + декоративные уголки. - blanket: 30 звёздочек (белые/жёлтые) разного размера на шумной тёмно-синей базе. - poster_csgo: диагональная штриховка + силуэт AWP по центру + пиксельный «CS:GO» текст внизу. - poster_morgen: круг с лицом, глаза-щели, рот + пиксельный «MS». - door: вертикальное волокно Perlin + 3 горизонтальных шва (панели) + 4 сучка разного размера. - desk_white: чистый белый + жёлтый Steam-стикер в углу + царапина. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
293 lines
12 KiB
GDScript
293 lines
12 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]п. 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 _to_license_intro() -> void:
|
||
current_step = Step.LICENSE_INTRO
|
||
title_label.text = "Шиндовс 9 — Шаг 1 из 5"
|
||
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 — Полный текст соглашения"
|
||
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"
|
||
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"
|
||
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"
|
||
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"
|
||
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 — Установка"
|
||
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()
|