From c3190ad05858ae48c48efebfc6a2f23afbe652dd Mon Sep 17 00:00:00 2001 From: Dmitrii Bykov Date: Sun, 24 May 2026 17:50:26 +0300 Subject: [PATCH] =?UTF-8?q?prologue:=20=D1=86=D0=B5=D0=BB=D0=B8=D0=BA?= =?UTF-8?q?=D0=BE=D0=BC=20=D0=90=D0=BA=D1=82=201=20(=D0=BA=D0=BE=D0=BC?= =?UTF-8?q?=D0=BD=D0=B0=D1=82=D0=B0+=D0=BA=D1=83=D1=85=D0=BD=D1=8F)=20+=20?= =?UTF-8?q?=D0=90=D0=BA=D1=82=202=20(=D1=83=D1=81=D1=82=D0=B0=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=BA=D0=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ИНФРАСТРУКТУРА: - GameState autoload: player_name, прогресс актов, ачивки. - SceneManager autoload: change_scene с deferred переключением, shortcuts go_room/go_kitchen/go_install. - TextureGen autoload: процедурные pixel-art ImageTexture через Image.set_pixel, кешируются в Dictionary. Текстуры: wallpaper (бежевая с растительным узором), linoleum (полосы паркета), ceiling (побелка с шумом), wall_carpet (бордовый с ромбами), blanket (тёмно-синее со звёздами), poster_csgo (жёлтый с силуэтом AWP), poster_morgen (тёмно-красный с головой), door (дерево с волокном), desk_white, book_color(0-4). АКТ 1 КОМНАТА (act1_room.tscn / act1.gd): - Все стены/пол/потолок/ковёр/одеяло/постеры/стол/дверь теперь с процедурными текстурами через TextureGen.apply_to_material с правильным uv1_scale. Texture filter NEAREST для пиксель-арта. - Door interactable добавлен в east-wall (рядом с DoorPanel). Включается после ухода отца. Клик → переход на кухню. - GameState persists прогресс — возврат из кухни не повторяет интро. - _setup_post_intro_state восстанавливает SEEK_DRIVE после кухни. - После подбора флешки — переход на act2_install. АКТ 1 КУХНЯ (act1_kitchen.tscn / act1_kitchen.gd): - 3×3м комнатка с холодильником «Атлант», плитой (4 ручки красные), столешницей+раковиной, чайником, столом+2 стула, окном+радиатором. - Wi-Fi записка приклеена на холодильнике, читается → ачивка «Wi-Fi разведчик» + GameState.wifi_password_found = true. - Дверь обратно в комнату — SceneManager.go_room(). - Текстуры через TextureGen. АКТ 2 УСТАНОВКА ШИНДОВС (act2_install.tscn / act2_install.gd): - Полная 2D-сцена с 7 шагами state machine: BOOT (4 квадратика логотипа 1.6 сек) → LICENSE_INTRO → LICENSE_FULL (по «Читать») → DISK → USERNAME → PASSWORD → TIMEZONE → INSTALLING (5 фаз прогресс-бара 3 сек) → COMPLETE. - ШИЗО-пункты лицензионки: 7.3.2, 11.4, 12.1, 15.7, 18.0, 21.3, 23.9, 27.1 (полный текст из канона Пролог Акт 2). - Ввод имени с валидацией: 2-24 символа, regex буквы+цифры, ban-list титулов (повелитель/владыка/бог/лорд/царь) и мата. - Подсказка-плейсхолдеры из канона: bossiara13, fr3sh_n3kit и т.п. - Пароль с чекбоксом «Без пароля» + ачивки «Главное чтоб удобно» (123/qwerty/empty) и «Параноик» (12+ символов со спец). - Часовой пояс OptionButton с 7 регионами страны Р, UTC+3 default. - Welcome-экран после установки: «Добро пожаловать, {playerName}». - Кнопка «Запустить стеме» — заглушка Акта 3. Co-Authored-By: Claude Opus 4.7 (1M context) --- project.godot | 3 + scenes/prologue/act1_kitchen.tscn | 408 ++++++++++++++++++++++++++ scenes/prologue/act1_room.tscn | 18 +- scenes/prologue/act2_install.tscn | 179 +++++++++++ scripts/prologue/act1.gd | 91 +++++- scripts/prologue/act1_kitchen.gd | 63 ++++ scripts/prologue/act1_kitchen.gd.uid | 1 + scripts/prologue/act2_install.gd | 292 ++++++++++++++++++ scripts/prologue/act2_install.gd.uid | 1 + scripts/util/game_state.gd | 44 +++ scripts/util/game_state.gd.uid | 1 + scripts/util/scene_manager.gd | 23 ++ scripts/util/scene_manager.gd.uid | 1 + scripts/util/texture_generator.gd | 269 +++++++++++++++++ scripts/util/texture_generator.gd.uid | 1 + 15 files changed, 1388 insertions(+), 7 deletions(-) create mode 100644 scenes/prologue/act1_kitchen.tscn create mode 100644 scenes/prologue/act2_install.tscn create mode 100644 scripts/prologue/act1_kitchen.gd create mode 100644 scripts/prologue/act1_kitchen.gd.uid create mode 100644 scripts/prologue/act2_install.gd create mode 100644 scripts/prologue/act2_install.gd.uid create mode 100644 scripts/util/game_state.gd create mode 100644 scripts/util/game_state.gd.uid create mode 100644 scripts/util/scene_manager.gd create mode 100644 scripts/util/scene_manager.gd.uid create mode 100644 scripts/util/texture_generator.gd create mode 100644 scripts/util/texture_generator.gd.uid diff --git a/project.godot b/project.godot index 40ec271..a1d7fd3 100644 --- a/project.godot +++ b/project.godot @@ -17,6 +17,9 @@ config/features=PackedStringArray("4.6", "GL Compatibility") [autoload] +GameState="*res://scripts/util/game_state.gd" +TextureGen="*res://scripts/util/texture_generator.gd" +SceneManager="*res://scripts/util/scene_manager.gd" DialogManager="*res://scenes/components/dialog_ui.tscn" [display] diff --git a/scenes/prologue/act1_kitchen.tscn b/scenes/prologue/act1_kitchen.tscn new file mode 100644 index 0000000..f9982eb --- /dev/null +++ b/scenes/prologue/act1_kitchen.tscn @@ -0,0 +1,408 @@ +[gd_scene load_steps=44 format=3] + +[ext_resource type="Script" path="res://scripts/prologue/act1_kitchen.gd" id="1_kit"] +[ext_resource type="Script" path="res://scripts/interactables/interactable.gd" id="2_int"] +[ext_resource type="PackedScene" path="res://scenes/components/player_fpv.tscn" id="3_player"] + +[sub_resource type="BoxMesh" id="mesh_floor"] +size = Vector3(3, 0.1, 3) + +[sub_resource type="BoxShape3D" id="shape_floor"] +size = Vector3(3, 0.1, 3) + +[sub_resource type="BoxMesh" id="mesh_wall_ns"] +size = Vector3(3, 2.5, 0.1) + +[sub_resource type="BoxShape3D" id="shape_wall_ns"] +size = Vector3(3, 2.5, 0.1) + +[sub_resource type="BoxMesh" id="mesh_wall_ew"] +size = Vector3(0.1, 2.5, 3) + +[sub_resource type="BoxShape3D" id="shape_wall_ew"] +size = Vector3(0.1, 2.5, 3) + +[sub_resource type="BoxMesh" id="mesh_fridge"] +size = Vector3(0.6, 1.8, 0.6) + +[sub_resource type="BoxShape3D" id="shape_fridge"] +size = Vector3(0.6, 1.8, 0.6) + +[sub_resource type="BoxMesh" id="mesh_stove"] +size = Vector3(0.6, 0.85, 0.6) + +[sub_resource type="BoxShape3D" id="shape_stove"] +size = Vector3(0.6, 0.85, 0.6) + +[sub_resource type="BoxMesh" id="mesh_counter"] +size = Vector3(0.7, 0.85, 0.6) + +[sub_resource type="BoxShape3D" id="shape_counter"] +size = Vector3(0.7, 0.85, 0.6) + +[sub_resource type="BoxMesh" id="mesh_sink"] +size = Vector3(0.5, 0.05, 0.4) + +[sub_resource type="BoxMesh" id="mesh_kitchen_window"] +size = Vector3(0.8, 0.5, 0.06) + +[sub_resource type="BoxMesh" id="mesh_door_panel"] +size = Vector3(1.0, 2.0, 0.06) + +[sub_resource type="BoxShape3D" id="shape_door"] +size = Vector3(1.0, 2.0, 0.06) + +[sub_resource type="BoxMesh" id="mesh_table"] +size = Vector3(0.9, 0.04, 0.7) + +[sub_resource type="BoxShape3D" id="shape_table"] +size = Vector3(0.9, 0.04, 0.7) + +[sub_resource type="BoxMesh" id="mesh_table_leg"] +size = Vector3(0.05, 0.72, 0.05) + +[sub_resource type="BoxMesh" id="mesh_chair_seat"] +size = Vector3(0.35, 0.04, 0.35) + +[sub_resource type="BoxShape3D" id="shape_chair_seat"] +size = Vector3(0.35, 0.04, 0.35) + +[sub_resource type="BoxMesh" id="mesh_chair_back"] +size = Vector3(0.35, 0.5, 0.04) + +[sub_resource type="BoxMesh" id="mesh_chair_leg"] +size = Vector3(0.04, 0.46, 0.04) + +[sub_resource type="BoxMesh" id="mesh_note"] +size = Vector3(0.15, 0.01, 0.1) + +[sub_resource type="BoxShape3D" id="shape_note"] +size = Vector3(0.15, 0.01, 0.1) + +[sub_resource type="BoxMesh" id="mesh_radiator"] +size = Vector3(0.7, 0.4, 0.08) + +[sub_resource type="BoxMesh" id="mesh_kettle"] +size = Vector3(0.18, 0.22, 0.18) + +[sub_resource type="BoxMesh" id="mesh_knob"] +size = Vector3(0.04, 0.04, 0.04) + +[sub_resource type="StandardMaterial3D" id="mat_floor"] +shading_mode = 0 +albedo_color = Color(0.72, 0.6, 0.41, 1) + +[sub_resource type="StandardMaterial3D" id="mat_wall"] +shading_mode = 0 +albedo_color = Color(0.83, 0.72, 0.55, 1) + +[sub_resource type="StandardMaterial3D" id="mat_ceiling"] +shading_mode = 0 +albedo_color = Color(0.92, 0.9, 0.85, 1) + +[sub_resource type="StandardMaterial3D" id="mat_fridge"] +shading_mode = 0 +albedo_color = Color(0.95, 0.94, 0.9, 1) + +[sub_resource type="StandardMaterial3D" id="mat_stove"] +shading_mode = 0 +albedo_color = Color(0.42, 0.42, 0.45, 1) + +[sub_resource type="StandardMaterial3D" id="mat_counter"] +shading_mode = 0 +albedo_color = Color(0.88, 0.86, 0.82, 1) + +[sub_resource type="StandardMaterial3D" id="mat_sink"] +shading_mode = 0 +albedo_color = Color(0.65, 0.65, 0.7, 1) + +[sub_resource type="StandardMaterial3D" id="mat_kitchen_window"] +shading_mode = 0 +albedo_color = Color(0.6, 0.85, 1.0, 1) +emission_enabled = true +emission = Color(0.6, 0.85, 1.0, 1) +emission_energy_multiplier = 0.6 + +[sub_resource type="StandardMaterial3D" id="mat_door"] +shading_mode = 0 +albedo_color = Color(0.85, 0.82, 0.75, 1) + +[sub_resource type="StandardMaterial3D" id="mat_table"] +shading_mode = 0 +albedo_color = Color(0.55, 0.38, 0.2, 1) + +[sub_resource type="StandardMaterial3D" id="mat_chair"] +shading_mode = 0 +albedo_color = Color(0.55, 0.38, 0.2, 1) + +[sub_resource type="StandardMaterial3D" id="mat_note"] +shading_mode = 0 +albedo_color = Color(0.95, 0.95, 0.88, 1) +emission_enabled = true +emission = Color(0.95, 0.95, 0.7, 1) +emission_energy_multiplier = 0.5 + +[sub_resource type="StandardMaterial3D" id="mat_radiator"] +shading_mode = 0 +albedo_color = Color(0.96, 0.94, 0.88, 1) + +[sub_resource type="StandardMaterial3D" id="mat_kettle"] +shading_mode = 0 +albedo_color = Color(0.85, 0.16, 0.16, 1) + +[sub_resource type="StandardMaterial3D" id="mat_knob"] +shading_mode = 0 +albedo_color = Color(0.85, 0.2, 0.2, 1) + +[node name="Act1Kitchen" type="Node3D"] +script = ExtResource("1_kit") + +[node name="Floor" type="StaticBody3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.05, 0) + +[node name="Mesh" type="MeshInstance3D" parent="Floor"] +mesh = SubResource("mesh_floor") +material_override = SubResource("mat_floor") + +[node name="Coll" type="CollisionShape3D" parent="Floor"] +shape = SubResource("shape_floor") + +[node name="Ceiling" type="StaticBody3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.55, 0) + +[node name="Mesh" type="MeshInstance3D" parent="Ceiling"] +mesh = SubResource("mesh_floor") +material_override = SubResource("mat_ceiling") + +[node name="Coll" type="CollisionShape3D" parent="Ceiling"] +shape = SubResource("shape_floor") + +[node name="WallN" type="StaticBody3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.25, -1.55) + +[node name="Mesh" type="MeshInstance3D" parent="WallN"] +mesh = SubResource("mesh_wall_ns") +material_override = SubResource("mat_wall") + +[node name="Coll" type="CollisionShape3D" parent="WallN"] +shape = SubResource("shape_wall_ns") + +[node name="Window" type="MeshInstance3D" parent="WallN"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.7, 0.35, 0.06) +mesh = SubResource("mesh_kitchen_window") +material_override = SubResource("mat_kitchen_window") + +[node name="Radiator" type="MeshInstance3D" parent="WallN"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.7, -0.7, 0.1) +mesh = SubResource("mesh_radiator") +material_override = SubResource("mat_radiator") + +[node name="WallS" type="StaticBody3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.25, 1.55) + +[node name="Mesh" type="MeshInstance3D" parent="WallS"] +mesh = SubResource("mesh_wall_ns") +material_override = SubResource("mat_wall") + +[node name="Coll" type="CollisionShape3D" parent="WallS"] +shape = SubResource("shape_wall_ns") + +[node name="DoorPanel" type="MeshInstance3D" parent="WallS"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.7, -0.25, -0.06) +mesh = SubResource("mesh_door_panel") +material_override = SubResource("mat_door") + +[node name="DoorBack" type="StaticBody3D" parent="WallS"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.7, -0.25, -0.12) +script = ExtResource("2_int") +prompt = "вернуться в комнату" + +[node name="Mesh" type="MeshInstance3D" parent="WallS/DoorBack"] +visible = false +mesh = SubResource("mesh_door_panel") + +[node name="Coll" type="CollisionShape3D" parent="WallS/DoorBack"] +shape = SubResource("shape_door") + +[node name="WallE" type="StaticBody3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.55, 1.25, 0) + +[node name="Mesh" type="MeshInstance3D" parent="WallE"] +mesh = SubResource("mesh_wall_ew") +material_override = SubResource("mat_wall") + +[node name="Coll" type="CollisionShape3D" parent="WallE"] +shape = SubResource("shape_wall_ew") + +[node name="WallW" type="StaticBody3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -1.55, 1.25, 0) + +[node name="Mesh" type="MeshInstance3D" parent="WallW"] +mesh = SubResource("mesh_wall_ew") +material_override = SubResource("mat_wall") + +[node name="Coll" type="CollisionShape3D" parent="WallW"] +shape = SubResource("shape_wall_ew") + +[node name="Fridge" type="StaticBody3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.9, 0.9, -1.15) + +[node name="Mesh" type="MeshInstance3D" parent="Fridge"] +mesh = SubResource("mesh_fridge") +material_override = SubResource("mat_fridge") + +[node name="Coll" type="CollisionShape3D" parent="Fridge"] +shape = SubResource("shape_fridge") + +[node name="WiFiNote" type="StaticBody3D" parent="Fridge"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.6, 0.31) +script = ExtResource("2_int") +prompt = "прочитать записку" + +[node name="Mesh" type="MeshInstance3D" parent="Fridge/WiFiNote"] +mesh = SubResource("mesh_note") +material_override = SubResource("mat_note") + +[node name="Coll" type="CollisionShape3D" parent="Fridge/WiFiNote"] +shape = SubResource("shape_note") + +[node name="Stove" type="StaticBody3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.0, 0.425, -1.2) + +[node name="Mesh" type="MeshInstance3D" parent="Stove"] +mesh = SubResource("mesh_stove") +material_override = SubResource("mat_stove") + +[node name="Coll" type="CollisionShape3D" parent="Stove"] +shape = SubResource("shape_stove") + +[node name="Knob1" type="MeshInstance3D" parent="Stove"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.18, 0.4, 0.32) +mesh = SubResource("mesh_knob") +material_override = SubResource("mat_knob") + +[node name="Knob2" type="MeshInstance3D" parent="Stove"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.18, 0.4, 0.32) +mesh = SubResource("mesh_knob") +material_override = SubResource("mat_knob") + +[node name="Counter" type="StaticBody3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.7, 0.425, -1.2) + +[node name="Mesh" type="MeshInstance3D" parent="Counter"] +mesh = SubResource("mesh_counter") +material_override = SubResource("mat_counter") + +[node name="Coll" type="CollisionShape3D" parent="Counter"] +shape = SubResource("shape_counter") + +[node name="Sink" type="MeshInstance3D" parent="Counter"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.45, 0.05) +mesh = SubResource("mesh_sink") +material_override = SubResource("mat_sink") + +[node name="Kettle" type="MeshInstance3D" parent="Stove"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.535, 0) +mesh = SubResource("mesh_kettle") +material_override = SubResource("mat_kettle") + +[node name="Table" type="StaticBody3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5, 0.73, 0.7) + +[node name="Mesh" type="MeshInstance3D" parent="Table"] +mesh = SubResource("mesh_table") +material_override = SubResource("mat_table") + +[node name="Coll" type="CollisionShape3D" parent="Table"] +shape = SubResource("shape_table") + +[node name="Leg1" type="MeshInstance3D" parent="Table"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.42, -0.38, -0.32) +mesh = SubResource("mesh_table_leg") +material_override = SubResource("mat_table") + +[node name="Leg2" type="MeshInstance3D" parent="Table"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.42, -0.38, -0.32) +mesh = SubResource("mesh_table_leg") +material_override = SubResource("mat_table") + +[node name="Leg3" type="MeshInstance3D" parent="Table"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.42, -0.38, 0.32) +mesh = SubResource("mesh_table_leg") +material_override = SubResource("mat_table") + +[node name="Leg4" type="MeshInstance3D" parent="Table"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.42, -0.38, 0.32) +mesh = SubResource("mesh_table_leg") +material_override = SubResource("mat_table") + +[node name="Chair1" type="StaticBody3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5, 0.48, 1.25) + +[node name="Mesh" type="MeshInstance3D" parent="Chair1"] +mesh = SubResource("mesh_chair_seat") +material_override = SubResource("mat_chair") + +[node name="Coll" type="CollisionShape3D" parent="Chair1"] +shape = SubResource("shape_chair_seat") + +[node name="Back" type="MeshInstance3D" parent="Chair1"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.27, 0.15) +mesh = SubResource("mesh_chair_back") +material_override = SubResource("mat_chair") + +[node name="Leg1" type="MeshInstance3D" parent="Chair1"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.15, -0.25, -0.15) +mesh = SubResource("mesh_chair_leg") +material_override = SubResource("mat_chair") + +[node name="Leg2" type="MeshInstance3D" parent="Chair1"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.15, -0.25, -0.15) +mesh = SubResource("mesh_chair_leg") +material_override = SubResource("mat_chair") + +[node name="Leg3" type="MeshInstance3D" parent="Chair1"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.15, -0.25, 0.15) +mesh = SubResource("mesh_chair_leg") +material_override = SubResource("mat_chair") + +[node name="Leg4" type="MeshInstance3D" parent="Chair1"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.15, -0.25, 0.15) +mesh = SubResource("mesh_chair_leg") +material_override = SubResource("mat_chair") + +[node name="Chair2" type="StaticBody3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5, 0.48, 0.1) + +[node name="Mesh" type="MeshInstance3D" parent="Chair2"] +mesh = SubResource("mesh_chair_seat") +material_override = SubResource("mat_chair") + +[node name="Coll" type="CollisionShape3D" parent="Chair2"] +shape = SubResource("shape_chair_seat") + +[node name="Leg1" type="MeshInstance3D" parent="Chair2"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.15, -0.25, -0.15) +mesh = SubResource("mesh_chair_leg") +material_override = SubResource("mat_chair") + +[node name="Leg2" type="MeshInstance3D" parent="Chair2"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.15, -0.25, -0.15) +mesh = SubResource("mesh_chair_leg") +material_override = SubResource("mat_chair") + +[node name="Leg3" type="MeshInstance3D" parent="Chair2"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.15, -0.25, 0.15) +mesh = SubResource("mesh_chair_leg") +material_override = SubResource("mat_chair") + +[node name="Leg4" type="MeshInstance3D" parent="Chair2"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.15, -0.25, 0.15) +mesh = SubResource("mesh_chair_leg") +material_override = SubResource("mat_chair") + +[node name="PlayerFPV" parent="." instance=ExtResource("3_player")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5, 0, 1.0) + +[connection signal="interacted" from="Fridge/WiFiNote" to="." method="_on_note_interacted"] +[connection signal="interacted" from="WallS/DoorBack" to="." method="_on_door_back_interacted"] diff --git a/scenes/prologue/act1_room.tscn b/scenes/prologue/act1_room.tscn index 60f4479..234a0fe 100644 --- a/scenes/prologue/act1_room.tscn +++ b/scenes/prologue/act1_room.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=76 format=3] +[gd_scene load_steps=77 format=3] [ext_resource type="Script" path="res://scripts/prologue/act1.gd" id="1_act1"] [ext_resource type="Script" path="res://scripts/interactables/interactable.gd" id="2_int"] @@ -40,6 +40,9 @@ size = Vector3(1.0, 0.5, 0.1) [sub_resource type="BoxMesh" id="mesh_door"] size = Vector3(0.06, 2.0, 1.0) +[sub_resource type="BoxShape3D" id="shape_door"] +size = Vector3(0.06, 2.0, 1.0) + [sub_resource type="BoxMesh" id="mesh_desk"] size = Vector3(1.5, 0.7, 0.6) @@ -393,6 +396,18 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.06, -0.25, 0) mesh = SubResource("mesh_door") material_override = SubResource("mat_door") +[node name="Door" type="StaticBody3D" parent="WallE"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.1, -0.25, 0) +script = ExtResource("2_int") +prompt = "выйти на кухню" + +[node name="Mesh" type="MeshInstance3D" parent="WallE/Door"] +visible = false +mesh = SubResource("mesh_door") + +[node name="Coll" type="CollisionShape3D" parent="WallE/Door"] +shape = SubResource("shape_door") + [node name="WallW" type="StaticBody3D" parent="."] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.05, 1.25, 0) @@ -565,3 +580,4 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0.5) [connection signal="interacted" from="Desk/FlashDrive" to="." method="_on_drive_interacted"] [connection signal="interacted" from="GiftBox" to="." method="_on_gift_interacted"] [connection signal="interacted" from="SystemUnit/Note" to="." method="_on_note_interacted"] +[connection signal="interacted" from="WallE/Door" to="." method="_on_door_interacted"] diff --git a/scenes/prologue/act2_install.tscn b/scenes/prologue/act2_install.tscn new file mode 100644 index 0000000..38c1872 --- /dev/null +++ b/scenes/prologue/act2_install.tscn @@ -0,0 +1,179 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://scripts/prologue/act2_install.gd" id="1_act2"] + +[node name="Act2Install" type="Control"] +layout_mode = 3 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_right = 640.0 +offset_bottom = 360.0 +script = ExtResource("1_act2") + +[node name="Desktop" type="ColorRect" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_right = 640.0 +offset_bottom = 360.0 +color = Color(0.04, 0.08, 0.18, 1) + +[node name="FourSquares" type="Control" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_right = 640.0 +offset_bottom = 360.0 + +[node name="LogoText" type="Label" parent="FourSquares"] +offset_left = 0.0 +offset_top = 130.0 +offset_right = 640.0 +offset_bottom = 160.0 +text = "ШИНДОВС 9" +horizontal_alignment = 1 +modulate = Color(0.94, 0.94, 0.94, 1) + +[node name="SqRed" type="ColorRect" parent="FourSquares"] +offset_left = 290.0 +offset_top = 170.0 +offset_right = 314.0 +offset_bottom = 194.0 +color = Color(0.9, 0.2, 0.2, 1) + +[node name="SqGreen" type="ColorRect" parent="FourSquares"] +offset_left = 316.0 +offset_top = 170.0 +offset_right = 340.0 +offset_bottom = 194.0 +color = Color(0.2, 0.78, 0.3, 1) + +[node name="SqBlue" type="ColorRect" parent="FourSquares"] +offset_left = 290.0 +offset_top = 196.0 +offset_right = 314.0 +offset_bottom = 220.0 +color = Color(0.2, 0.4, 0.9, 1) + +[node name="SqYellow" type="ColorRect" parent="FourSquares"] +offset_left = 316.0 +offset_top = 196.0 +offset_right = 340.0 +offset_bottom = 220.0 +color = Color(0.94, 0.71, 0.1, 1) + +[node name="BootHint" type="Label" parent="FourSquares"] +offset_left = 0.0 +offset_top = 240.0 +offset_right = 640.0 +offset_bottom = 260.0 +text = "Подготовка установки…" +horizontal_alignment = 1 +modulate = Color(0.7, 0.7, 0.7, 1) + +[node name="Center" type="CenterContainer" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_right = 640.0 +offset_bottom = 360.0 + +[node name="Window" type="PanelContainer" parent="Center"] +custom_minimum_size = Vector2(540, 300) +layout_mode = 2 + +[node name="V" type="VBoxContainer" parent="Center/Window"] +layout_mode = 2 + +[node name="TitleBar" type="PanelContainer" parent="Center/Window/V"] +layout_mode = 2 + +[node name="Title" type="Label" parent="Center/Window/V/TitleBar"] +layout_mode = 2 +text = "Шиндовс 9 — Установка" +horizontal_alignment = 1 +modulate = Color(0.94, 0.71, 0.1, 1) + +[node name="Body" type="RichTextLabel" parent="Center/Window/V"] +custom_minimum_size = Vector2(520, 160) +layout_mode = 2 +bbcode_enabled = true +fit_content = true +scroll_active = true +text = "" + +[node name="Input" type="LineEdit" parent="Center/Window/V"] +layout_mode = 2 +placeholder_text = "..." +max_length = 24 + +[node name="InputHint" type="Label" parent="Center/Window/V"] +layout_mode = 2 +text = "" +autowrap_mode = 3 +modulate = Color(0.55, 0.55, 0.55, 1) + +[node name="CheckBoxNoPwd" type="CheckBox" parent="Center/Window/V"] +layout_mode = 2 +text = "Без пароля" + +[node name="TimeZone" type="OptionButton" parent="Center/Window/V"] +layout_mode = 2 + +[node name="ErrorLabel" type="Label" parent="Center/Window/V"] +layout_mode = 2 +text = "" +modulate = Color(0.85, 0.16, 0.16, 1) + +[node name="Progress" type="ProgressBar" parent="Center/Window/V"] +layout_mode = 2 +max_value = 100.0 +show_percentage = true + +[node name="Buttons" type="HBoxContainer" parent="Center/Window/V"] +layout_mode = 2 +alignment = 2 + +[node name="BtnB" type="Button" parent="Center/Window/V/Buttons"] +layout_mode = 2 +text = "Читать" + +[node name="BtnA" type="Button" parent="Center/Window/V/Buttons"] +layout_mode = 2 +text = "Принять" + +[node name="WelcomeScreen" type="Control" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_right = 640.0 +offset_bottom = 360.0 +visible = false + +[node name="BG" type="ColorRect" parent="WelcomeScreen"] +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_right = 640.0 +offset_bottom = 360.0 +color = Color(0.04, 0.08, 0.18, 1) + +[node name="V" type="VBoxContainer" parent="WelcomeScreen"] +offset_left = 0.0 +offset_top = 130.0 +offset_right = 640.0 +offset_bottom = 240.0 +alignment = 1 + +[node name="WelcomeText" type="Label" parent="WelcomeScreen/V"] +layout_mode = 2 +text = "Добро пожаловать." +horizontal_alignment = 1 +modulate = Color(0.94, 0.94, 0.94, 1) + +[node name="Hint" type="Label" parent="WelcomeScreen/V"] +layout_mode = 2 +text = "" +horizontal_alignment = 1 +modulate = Color(0.7, 0.7, 0.7, 1) + +[node name="LaunchSteme" type="Button" parent="WelcomeScreen/V"] +layout_mode = 2 +text = "Запустить стеме" + +[connection signal="pressed" from="WelcomeScreen/V/LaunchSteme" to="." method="_on_launch_steme"] diff --git a/scripts/prologue/act1.gd b/scripts/prologue/act1.gd index fee0dd1..d082015 100644 --- a/scripts/prologue/act1.gd +++ b/scripts/prologue/act1.gd @@ -5,6 +5,7 @@ extends Node3D @onready var system_unit: Node3D = $SystemUnit @onready var note: Interactable = $SystemUnit/Note @onready var flash_drive: Interactable = $Desk/FlashDrive +@onready var door: Interactable = $WallE/Door @onready var father: Node3D = $FatherNPC enum State { INTRO, EXPLORE_GIFT, INSIDE_GIFT, NOTE_READ, FATHER_FAREWELL, SEEK_DRIVE, ENDING } @@ -12,16 +13,67 @@ var state: State = State.INTRO func _ready() -> void: + _apply_textures() + + player.interactable_focused.connect(_on_player_focused) + player.interactable_unfocused.connect(_on_player_unfocused) + + DialogManager.set_date_time(GameState.game_date, GameState.game_time) + + # Если возвращаемся из кухни — пропускаем интро + if GameState.act1_father_left: + _setup_post_intro_state() + else: + await _play_intro() + + +func _apply_textures() -> void: + var floor_mat: BaseMaterial3D = ($Floor/Mesh as MeshInstance3D).material_override + TextureGen.apply_to_material(floor_mat, TextureGen.linoleum(), Vector3(8, 8, 8)) + + var ceiling_mat: BaseMaterial3D = ($Ceiling/Mesh as MeshInstance3D).material_override + TextureGen.apply_to_material(ceiling_mat, TextureGen.ceiling(), Vector3(6, 6, 6)) + + for wall_path in ["WallN", "WallS", "WallE", "WallW"]: + var wall_node: Node = get_node(wall_path) + var wall_mat: BaseMaterial3D = (wall_node.get_node("Mesh") as MeshInstance3D).material_override + TextureGen.apply_to_material(wall_mat, TextureGen.wallpaper(), Vector3(8, 5, 8)) + + var carpet_mat: BaseMaterial3D = ($WallS/WallCarpet as MeshInstance3D).material_override + TextureGen.apply_to_material(carpet_mat, TextureGen.wall_carpet(), Vector3(1, 1, 1)) + + var blanket_mat: BaseMaterial3D = ($Bed/Blanket as MeshInstance3D).material_override + TextureGen.apply_to_material(blanket_mat, TextureGen.blanket(), Vector3(2, 2, 2)) + + var csgo_mat: BaseMaterial3D = ($WallN/PosterCSGO as MeshInstance3D).material_override + TextureGen.apply_to_material(csgo_mat, TextureGen.poster_csgo(), Vector3(1, 1, 1)) + + var morgen_mat: BaseMaterial3D = ($WallN/PosterMorgen as MeshInstance3D).material_override + TextureGen.apply_to_material(morgen_mat, TextureGen.poster_morgen(), Vector3(1, 1, 1)) + + var desk_mat: BaseMaterial3D = ($Desk/Mesh as MeshInstance3D).material_override + TextureGen.apply_to_material(desk_mat, TextureGen.desk_white(), Vector3(2, 2, 2)) + + var door_panel_mat: BaseMaterial3D = ($WallE/DoorPanel as MeshInstance3D).material_override + TextureGen.apply_to_material(door_panel_mat, TextureGen.door(), Vector3(1, 1, 1)) + + # Книги + var book_letters: Array[String] = ["A", "B", "C", "D", "E"] + for i in range(book_letters.size()): + var book_node: Node = $Shelf.get_node("Book" + book_letters[i]) + if book_node != null and book_node is MeshInstance3D: + var book_mat: BaseMaterial3D = (book_node as MeshInstance3D).material_override + TextureGen.apply_to_material(book_mat, TextureGen.book_color(i), Vector3(1, 1, 1)) + + +func _play_intro() -> void: system_unit.visible = false note.visible = false gift_box.set_interactable(false) flash_drive.set_interactable(false) + door.set_interactable(false) player.lock_input() - player.interactable_focused.connect(_on_player_focused) - player.interactable_unfocused.connect(_on_player_unfocused) - - DialogManager.set_date_time("21 АВГ 2020 · ПТ", "10:30") DialogManager.set_objective("Распакуй подарок от бати") await get_tree().create_timer(0.8).timeout @@ -33,6 +85,20 @@ func _ready() -> void: player.unlock_input() +func _setup_post_intro_state() -> void: + # Возвращение из кухни — отец уже ушёл + system_unit.visible = true + note.visible = false + gift_box.visible = false + gift_box.set_interactable(false) + note.set_interactable(false) + father.visible = false + state = State.SEEK_DRIVE + flash_drive.set_interactable(true) + door.set_interactable(true) + DialogManager.set_objective("Возьми флешку с Шиндовс") + + func _on_player_focused(obj: Node) -> void: if obj.has_method("get_prompt"): DialogManager.set_prompt(obj.get_prompt()) @@ -46,6 +112,7 @@ func _on_gift_interacted(_p: Node) -> void: if state != State.EXPLORE_GIFT: return state = State.INSIDE_GIFT + GameState.act1_gift_opened = true gift_box.set_interactable(false) gift_box.visible = false system_unit.visible = true @@ -59,6 +126,7 @@ func _on_note_interacted(_p: Node) -> void: if state != State.INSIDE_GIFT: return state = State.NOTE_READ + GameState.act1_note_read = true note.set_interactable(false) player.lock_input() DialogManager.clear_prompt() @@ -73,22 +141,33 @@ func _on_note_interacted(_p: Node) -> void: tween.tween_property(father, "position:x", father.position.x - 2.5, 0.8) await tween.finished father.visible = false + GameState.act1_father_left = true state = State.SEEK_DRIVE flash_drive.set_interactable(true) + door.set_interactable(true) DialogManager.set_objective("Возьми флешку с Шиндовс") player.unlock_input() +func _on_door_interacted(_p: Node) -> void: + if state != State.SEEK_DRIVE: + return + SceneManager.go_kitchen() + + func _on_drive_interacted(_p: Node) -> void: if state != State.SEEK_DRIVE: return state = State.ENDING + GameState.act1_drive_taken = true flash_drive.set_interactable(false) + door.set_interactable(false) player.lock_input() DialogManager.clear_prompt() DialogManager.set_objective("") await DialogManager.think("Ну ладно. Андрюхино так Андрюхино. Поехали.") - await DialogManager.say("СИСТЕМА", "Конец Акта 1. Акт 2 — Установка Шиндовс (TBD).") - get_tree().quit() + await DialogManager.say("СИСТЕМА", "Конец Акта 1. Запускаю Акт 2 — Установка Шиндовс…") + GameState.game_time = "10:42" + SceneManager.go_install() diff --git a/scripts/prologue/act1_kitchen.gd b/scripts/prologue/act1_kitchen.gd new file mode 100644 index 0000000..c2796a9 --- /dev/null +++ b/scripts/prologue/act1_kitchen.gd @@ -0,0 +1,63 @@ +extends Node3D + +@onready var player: FPVController = $PlayerFPV +@onready var fridge_note: Interactable = $Fridge/WiFiNote +@onready var door_back: Interactable = $WallS/DoorBack + + +func _ready() -> void: + _apply_textures() + player.interactable_focused.connect(_on_player_focused) + player.interactable_unfocused.connect(_on_player_unfocused) + + if GameState.wifi_password_found: + fridge_note.set_interactable(false) + else: + fridge_note.set_interactable(true) + door_back.set_interactable(true) + + DialogManager.set_date_time(GameState.game_date, GameState.game_time) + if GameState.wifi_password_found: + DialogManager.set_objective("Вернись в комнату — флешка ждёт") + else: + DialogManager.set_objective("Найди пароль Wi-Fi на холодильнике") + + +func _apply_textures() -> void: + var floor_mat: BaseMaterial3D = ($Floor/Mesh as MeshInstance3D).material_override + TextureGen.apply_to_material(floor_mat, TextureGen.linoleum(), Vector3(6, 6, 6)) + + var ceiling_mat: BaseMaterial3D = ($Ceiling/Mesh as MeshInstance3D).material_override + TextureGen.apply_to_material(ceiling_mat, TextureGen.ceiling(), Vector3(4, 4, 4)) + + for wall_path in ["WallN", "WallS", "WallE", "WallW"]: + var wall_node: Node = get_node(wall_path) + var wall_mat: BaseMaterial3D = (wall_node.get_node("Mesh") as MeshInstance3D).material_override + TextureGen.apply_to_material(wall_mat, TextureGen.wallpaper(), Vector3(6, 5, 6)) + + var door_panel_mat: BaseMaterial3D = ($WallS/DoorPanel as MeshInstance3D).material_override + TextureGen.apply_to_material(door_panel_mat, TextureGen.door(), Vector3(1, 1, 1)) + + +func _on_player_focused(obj: Node) -> void: + if obj.has_method("get_prompt"): + DialogManager.set_prompt(obj.get_prompt()) + + +func _on_player_unfocused() -> void: + DialogManager.clear_prompt() + + +func _on_note_interacted(_p: Node) -> void: + player.lock_input() + DialogManager.clear_prompt() + await DialogManager.show_note("«Wi-Fi: qwerty. Не парься.»\n— Бать") + fridge_note.set_interactable(false) + GameState.wifi_password_found = true + GameState.unlock_achievement("Wi-Fi разведчик") + DialogManager.set_objective("Вернись в комнату — флешка ждёт") + player.unlock_input() + + +func _on_door_back_interacted(_p: Node) -> void: + SceneManager.go_room() diff --git a/scripts/prologue/act1_kitchen.gd.uid b/scripts/prologue/act1_kitchen.gd.uid new file mode 100644 index 0000000..b93c3cf --- /dev/null +++ b/scripts/prologue/act1_kitchen.gd.uid @@ -0,0 +1 @@ +uid://b8q17iyr3w18w diff --git a/scripts/prologue/act2_install.gd b/scripts/prologue/act2_install.gd new file mode 100644 index 0000000..6a54526 --- /dev/null +++ b/scripts/prologue/act2_install.gd @@ -0,0 +1,292 @@ +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_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() diff --git a/scripts/prologue/act2_install.gd.uid b/scripts/prologue/act2_install.gd.uid new file mode 100644 index 0000000..9382ea2 --- /dev/null +++ b/scripts/prologue/act2_install.gd.uid @@ -0,0 +1 @@ +uid://d1jqnip0wdm61 diff --git a/scripts/util/game_state.gd b/scripts/util/game_state.gd new file mode 100644 index 0000000..5ebcc84 --- /dev/null +++ b/scripts/util/game_state.gd @@ -0,0 +1,44 @@ +extends Node + +# Имя игрока (вводится в Акте 2) +var player_name: String = "" + +# Прогресс Акта 1 +var act1_gift_opened: bool = false +var act1_note_read: bool = false +var act1_father_left: bool = false +var act1_drive_taken: bool = false + +# Кухня (опц. визит) +var wifi_password_found: bool = false + +# Прогресс Акта 2 (установка Шиндовс) +var act2_license_read: bool = false +var act2_password_set: bool = false +var act2_timezone: String = "UTC+3 (С., страна Р)" + +# Дата/время игровое +var game_date: String = "21 АВГ 2020 · ПТ" +var game_time: String = "10:30" + +# Ачивки — логируются в консоль, UI на будущее +var achievements: Array[String] = [] + + +func unlock_achievement(ach: String) -> void: + if achievements.has(ach): + return + achievements.append(ach) + print("[ACHIEVEMENT] %s" % ach) + + +func reset() -> void: + player_name = "" + act1_gift_opened = false + act1_note_read = false + act1_father_left = false + act1_drive_taken = false + wifi_password_found = false + act2_license_read = false + act2_password_set = false + achievements.clear() diff --git a/scripts/util/game_state.gd.uid b/scripts/util/game_state.gd.uid new file mode 100644 index 0000000..fe837dc --- /dev/null +++ b/scripts/util/game_state.gd.uid @@ -0,0 +1 @@ +uid://c04shmfmiq1e4 diff --git a/scripts/util/scene_manager.gd b/scripts/util/scene_manager.gd new file mode 100644 index 0000000..a32da5d --- /dev/null +++ b/scripts/util/scene_manager.gd @@ -0,0 +1,23 @@ +extends Node + +const SCENE_ACT1_ROOM: String = "res://scenes/prologue/act1_room.tscn" +const SCENE_ACT1_KITCHEN: String = "res://scenes/prologue/act1_kitchen.tscn" +const SCENE_ACT2_INSTALL: String = "res://scenes/prologue/act2_install.tscn" + + +func change_scene(path: String) -> void: + # Освобождаем мышь, чтобы 2D-сцены не были в captured-mode + Input.mouse_mode = Input.MOUSE_MODE_VISIBLE + get_tree().change_scene_to_file.call_deferred(path) + + +func go_room() -> void: + change_scene(SCENE_ACT1_ROOM) + + +func go_kitchen() -> void: + change_scene(SCENE_ACT1_KITCHEN) + + +func go_install() -> void: + change_scene(SCENE_ACT2_INSTALL) diff --git a/scripts/util/scene_manager.gd.uid b/scripts/util/scene_manager.gd.uid new file mode 100644 index 0000000..a556078 --- /dev/null +++ b/scripts/util/scene_manager.gd.uid @@ -0,0 +1 @@ +uid://2avvxvmc8bcq diff --git a/scripts/util/texture_generator.gd b/scripts/util/texture_generator.gd new file mode 100644 index 0000000..58cd8cd --- /dev/null +++ b/scripts/util/texture_generator.gd @@ -0,0 +1,269 @@ +extends Node + +var _cache: Dictionary = {} + + +func _make_image(w: int, h: int, fill: Color) -> Image: + var img: Image = Image.create(w, h, false, Image.FORMAT_RGB8) + img.fill(fill) + return img + + +func _to_texture(img: Image) -> ImageTexture: + return ImageTexture.create_from_image(img) + + +func _get_or_create(key: String, maker: Callable) -> ImageTexture: + if _cache.has(key): + return _cache[key] + var tex: ImageTexture = maker.call() + _cache[key] = tex + return tex + + +# Бежевые обои с растительным узором (мелкие точки) +func wallpaper() -> ImageTexture: + return _get_or_create("wallpaper", func() -> ImageTexture: + var base: Color = Color(0.83, 0.72, 0.55) + var dot: Color = Color(0.72, 0.6, 0.42) + var darker: Color = Color(0.78, 0.68, 0.5) + var img: Image = _make_image(64, 64, base) + # мелкие крестики-цветы + for y in range(0, 64, 8): + for x in range(0, 64, 8): + var ox: int = 4 if (y / 8) % 2 == 0 else 0 + var px: int = (x + ox) % 64 + img.set_pixel(px, y, dot) + if px + 1 < 64: + img.set_pixel(px + 1, y, darker) + if y + 1 < 64: + img.set_pixel(px, y + 1, darker) + return _to_texture(img) + ) + + +# Линолеум жёлто-коричневый с горизонтальными полосами под паркет +func linoleum() -> ImageTexture: + return _get_or_create("linoleum", func() -> ImageTexture: + var a: Color = Color(0.72, 0.6, 0.41) + var b: Color = Color(0.65, 0.53, 0.36) + var c: Color = Color(0.58, 0.46, 0.3) + var img: Image = _make_image(64, 64, a) + # полосы паркета + for y in range(64): + var stripe: int = y / 8 + var col: Color = a if stripe % 2 == 0 else b + for x in range(64): + img.set_pixel(x, y, col) + # швы между досками + for y in range(0, 64, 8): + for x in range(64): + img.set_pixel(x, y, c) + # вертикальные швы пэчворк + for y in range(64): + var stripe: int = y / 8 + var seam_x: int = ((stripe * 23) % 32) + 16 + img.set_pixel(seam_x, y, c) + return _to_texture(img) + ) + + +# Потолок: светлая побелка с лёгким шумом +func ceiling() -> ImageTexture: + return _get_or_create("ceiling", func() -> ImageTexture: + var base: Color = Color(0.92, 0.9, 0.85) + var spot: Color = Color(0.88, 0.86, 0.82) + var img: Image = _make_image(32, 32, base) + var rng: RandomNumberGenerator = RandomNumberGenerator.new() + rng.seed = 4242 + for i in range(20): + var x: int = rng.randi_range(0, 31) + var y: int = rng.randi_range(0, 31) + img.set_pixel(x, y, spot) + return _to_texture(img) + ) + + +# Бордовый ковёр с персидским ромбовидным узором +func wall_carpet() -> ImageTexture: + return _get_or_create("wall_carpet", func() -> ImageTexture: + var base: Color = Color(0.36, 0.12, 0.13) + var pattern: Color = Color(0.78, 0.62, 0.2) + var dark: Color = Color(0.22, 0.07, 0.08) + var img: Image = _make_image(64, 64, base) + # ромбы + for cy in [16, 48]: + for cx in [16, 48]: + for r in range(6): + var px1: int = cx - r + var px2: int = cx + r + var py1: int = cy - (6 - r) + var py2: int = cy + (6 - r) + if px1 >= 0 and py1 >= 0 and px1 < 64 and py1 < 64: + img.set_pixel(px1, py1, pattern) + if px2 < 64 and py1 >= 0: + img.set_pixel(px2, py1, pattern) + if px1 >= 0 and py2 < 64: + img.set_pixel(px1, py2, pattern) + if px2 < 64 and py2 < 64: + img.set_pixel(px2, py2, pattern) + # рамка + for i in range(64): + img.set_pixel(i, 0, dark) + img.set_pixel(i, 63, dark) + img.set_pixel(0, i, dark) + img.set_pixel(63, i, dark) + return _to_texture(img) + ) + + +# Тёмно-синее одеяло «звёзды» +func blanket() -> ImageTexture: + return _get_or_create("blanket", func() -> ImageTexture: + var base: Color = Color(0.13, 0.16, 0.32) + var star: Color = Color(0.85, 0.85, 0.75) + var img: Image = _make_image(32, 32, base) + var stars: Array = [[5,5],[14,3],[24,7],[8,12],[20,16],[3,21],[27,22],[15,26],[10,28]] + for s in stars: + img.set_pixel(s[0], s[1], star) + if s[0] + 1 < 32: + img.set_pixel(s[0] + 1, s[1], star) + if s[0] - 1 >= 0: + img.set_pixel(s[0] - 1, s[1], star) + if s[1] + 1 < 32: + img.set_pixel(s[0], s[1] + 1, star) + if s[1] - 1 >= 0: + img.set_pixel(s[0], s[1] - 1, star) + return _to_texture(img) + ) + + +# CS:GO постер (жёлтый с силуэтом AWP) +func poster_csgo() -> ImageTexture: + return _get_or_create("poster_csgo", func() -> ImageTexture: + var base: Color = Color(0.94, 0.71, 0.1) + var dark: Color = Color(0.18, 0.13, 0.08) + var img: Image = _make_image(32, 40, base) + # рамка + for i in range(32): + img.set_pixel(i, 0, dark) + img.set_pixel(i, 39, dark) + for i in range(40): + img.set_pixel(0, i, dark) + img.set_pixel(31, i, dark) + # буквы CS внизу (примитивно) + for x in range(8, 12): + img.set_pixel(x, 32, dark) + img.set_pixel(x, 35, dark) + img.set_pixel(8, 33, dark) + img.set_pixel(8, 34, dark) + for x in range(14, 18): + img.set_pixel(x, 32, dark) + img.set_pixel(x, 35, dark) + img.set_pixel(14, 33, dark) + img.set_pixel(17, 34, dark) + # силуэт AWP (горизонталь сверху) + for x in range(6, 26): + img.set_pixel(x, 18, dark) + img.set_pixel(x, 19, dark) + for x in range(20, 26): + img.set_pixel(x, 17, dark) + img.set_pixel(7, 20, dark) + img.set_pixel(8, 20, dark) + return _to_texture(img) + ) + + +# Моргенштерн постер (тёмно-красный с белым контуром головы) +func poster_morgen() -> ImageTexture: + return _get_or_create("poster_morgen", func() -> ImageTexture: + var base: Color = Color(0.7, 0.13, 0.13) + var light: Color = Color(0.9, 0.85, 0.8) + var dark: Color = Color(0.2, 0.05, 0.05) + var img: Image = _make_image(32, 40, base) + # рамка + for i in range(32): + img.set_pixel(i, 0, dark) + img.set_pixel(i, 39, dark) + for i in range(40): + img.set_pixel(0, i, dark) + img.set_pixel(31, i, dark) + # силуэт головы по центру + var cx: int = 16 + var cy: int = 18 + for r in range(7): + var w: int = int(sqrt(49 - r * r)) + for dx in range(-w, w + 1): + if cy - r >= 0 and cy - r < 40 and cx + dx >= 0 and cx + dx < 32: + img.set_pixel(cx + dx, cy - r, light) + if cy + r < 40 and cx + dx >= 0 and cx + dx < 32: + img.set_pixel(cx + dx, cy + r, light) + return _to_texture(img) + ) + + +# Двери: деревянная текстура с волокном +func door() -> ImageTexture: + return _get_or_create("door", func() -> ImageTexture: + var a: Color = Color(0.36, 0.23, 0.12) + var b: Color = Color(0.45, 0.31, 0.18) + var c: Color = Color(0.28, 0.18, 0.09) + var img: Image = _make_image(32, 64, a) + for y in range(64): + for x in range(32): + var v: float = sin(float(x) * 0.5 + float(y) * 0.1) * 0.5 + 0.5 + if v > 0.6: + img.set_pixel(x, y, b) + elif v < 0.3: + img.set_pixel(x, y, c) + return _to_texture(img) + ) + + +# Стол MICKE: белый с малозаметной шумной текстурой +func desk_white() -> ImageTexture: + return _get_or_create("desk_white", func() -> ImageTexture: + var base: Color = Color(0.96, 0.95, 0.93) + var nick: Color = Color(0.88, 0.86, 0.82) + var img: Image = _make_image(32, 32, base) + var rng: RandomNumberGenerator = RandomNumberGenerator.new() + rng.seed = 1337 + for i in range(8): + var x: int = rng.randi_range(0, 31) + var y: int = rng.randi_range(0, 31) + img.set_pixel(x, y, nick) + return _to_texture(img) + ) + + +# Книжный корешок (несколько цветов) +func book_color(color_idx: int) -> ImageTexture: + var key: String = "book_%d" % color_idx + return _get_or_create(key, func() -> ImageTexture: + var palette: Array[Color] = [ + Color(0.36, 0.18, 0.15), + Color(0.2, 0.35, 0.5), + Color(0.13, 0.13, 0.15), + Color(0.55, 0.45, 0.25), + Color(0.3, 0.5, 0.3), + ] + var base: Color = palette[color_idx % palette.size()] + var trim: Color = Color(base.r * 0.6, base.g * 0.6, base.b * 0.6) + var img: Image = _make_image(16, 32, base) + # узкие линии-трим сверху и снизу + for x in range(16): + img.set_pixel(x, 0, trim) + img.set_pixel(x, 31, trim) + img.set_pixel(x, 5, trim) + img.set_pixel(x, 26, trim) + return _to_texture(img) + ) + + +# Применить текстуру к материалу с заданным uv-scale +func apply_to_material(mat: BaseMaterial3D, texture: ImageTexture, scale: Vector3 = Vector3.ONE) -> void: + if mat == null: + return + mat.albedo_texture = texture + mat.uv1_scale = scale + mat.texture_filter = BaseMaterial3D.TEXTURE_FILTER_NEAREST # пиксель-арт diff --git a/scripts/util/texture_generator.gd.uid b/scripts/util/texture_generator.gd.uid new file mode 100644 index 0000000..2335dca --- /dev/null +++ b/scripts/util/texture_generator.gd.uid @@ -0,0 +1 @@ +uid://bdqg3aapmg71d