farming-simulator/scripts/prologue/act1.gd
Dmitrii Bykov c3190ad058
Some checks failed
Godot CI / Import + smoke test (push) Failing after 29s
prologue: целиком Акт 1 (комната+кухня) + Акт 2 (установка)
ИНФРАСТРУКТУРА:
- 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) <noreply@anthropic.com>
2026-05-24 17:50:26 +03:00

173 lines
6.3 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 Node3D
@onready var player: FPVController = $PlayerFPV
@onready var gift_box: Interactable = $GiftBox
@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 }
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()
DialogManager.set_objective("Распакуй подарок от бати")
await get_tree().create_timer(0.8).timeout
await DialogManager.say("ОТЕЦ", "Ну? Чё стоишь. Распаковывай давай. Сам собирал, между прочим — полночи не спал.")
await DialogManager.think("Так… новый пека? Серьёзно? После моего динозавра?")
state = State.EXPLORE_GIFT
gift_box.set_interactable(true)
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())
func _on_player_unfocused() -> void:
DialogManager.clear_prompt()
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
note.visible = true
note.set_interactable(true)
DialogManager.clear_prompt()
DialogManager.set_objective("Прочитай записку")
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()
await DialogManager.show_note("«Не сожги. Пароль от Wi-Fi на холодильнике. — Бать»")
await DialogManager.think("Бать ну ты красавчик конечно.")
state = State.FATHER_FAREWELL
await DialogManager.say("ОТЕЦ", "Флешка с Шиндой на столе. Ставь сам, не маленький. Если запорешь — сам и переставляй.")
var tween: Tween = create_tween()
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 — Установка Шиндовс…")
GameState.game_time = "10:42"
SceneManager.go_install()