cleanup: remove md/txt files, add README, various fixes

This commit is contained in:
loki5512344 2026-06-15 14:00:38 +02:00
parent bb293c5d03
commit 4482b3a0ea
13 changed files with 110 additions and 1459 deletions

View file

@ -1,489 +0,0 @@
# Dead by Daylight Plugin — Руководство команд
**Версия:** 1.2
**Обновлено:** Июнь 2026
**Платформа:** Minecraft 1.21.5 (Paper)
---
## Содержание
1. [Фазы игры](#фазы-игры)
2. [Команды игроков — /dbd](#команды-игроков--dbd)
3. [Команды администратора — /dbdadmin](#команды-администратора--dbdadmin)
4. [Команды настройки карты — /dbdsetup](#команды-настройки-карты--dbdsetup)
5. [Права доступа](#права-доступа)
6. [Порядок настройки сервера](#порядок-настройки-сервера)
7. [Таблица всех команд](#таблица-всех-команд)
---
## Фазы игры
```
LOBBY
Игроки делают /dbd join → телепорт в мир lobby
Action bar показывает: ▶ Старт через: 45с ████████░░ (3/5)
Когда набралось минимум игроков — идёт отсчёт 60 сек
При нехватке игроков — таймер сбрасывается
↓
ROLE ASSIGN (5 сек)
Случайно выбирается 1 киллер, остальные — выжившие
Показывается тайтл с ролью
Все телепортируются на свои спавны в мире game
↓
GAMEPLAY
Выжившие чинят генераторы (нужно 5 из 7)
Киллер охотится
При починке всех нужных генераторов — открываются ворота
↓
ENDGAME (2 минуты)
Выжившие открывают ворота и убегают
Или используют люк (если остался 1 выживший)
Коллапс через 120 сек убивает всех оставшихся
```
---
## Команды игроков — /dbd
Все команды ниже доступны любому игроку (`mdbd.player`, по умолчанию `true`).
---
### /dbd join
Присоединиться к игровой очереди.
```
/dbd join
```
- Добавляет игрока в текущую сессию как выжившего (роль назначится позже)
- Телепортирует в мир `lobby`
- В action bar начинает отображаться счётчик и таймер
- Если игра не создана — сообщение об ошибке
```
→ [DBD] Вы присоединились к игре!
→ [DBD] PlayerName присоединился к очереди! (1/5)
```
---
### /dbd leave
Покинуть сессию.
```
/dbd leave
```
- В лобби: просто убирает из очереди, таймер может сброситься
- В игре: выживший считается мёртвым, киллер завершает игру
- Игрок телепортируется обратно в `lobby`
```
→ [DBD] Вы покинули игру
```
---
### /dbd lobby
Телепортироваться в лобби-мир.
```
/dbd lobby
```
- Работает в любой момент, не убирает из сессии
- Телепортирует на точку спавна лобби (из `locations.yml`) или на спавн мира `lobby`
```
→ [DBD] Вы телепортированы в лобби!
```
---
## Команды администратора — /dbd (admin)
Требуют право `mdbd.admin` (по умолчанию только OP).
---
### /dbd create
Создать новую игровую сессию.
```
/dbd create
```
- Создаёт сессию и открывает лобби
- Нельзя создать вторую сессию пока первая активна
```
→ [DBD] Игра создана!
→ [DBD] Игра уже существует (если уже есть)
```
---
### /dbd forcestart
Принудительно запустить игру, пропустив таймер.
```
/dbd forcestart
```
- Работает только если сессия в фазе LOBBY
- Игроков в очереди может быть меньше минимума
```
→ [DBD] Игра форсирована!
```
---
### /dbd stop
Немедленно завершить текущую сессию.
```
/dbd stop
```
- Все игроки телепортируются в `lobby`
- Сессия уничтожается, можно создавать новую
```
→ [DBD] Игра остановлена
```
---
### /dbd reload
Перезагрузить все конфиги без перезапуска сервера.
```
/dbd reload
```
Перезагружает: `config.yml`, `game.yml`, `messages.yml`, `skillcheck.yml`, `locations.yml`, `generators.yml`
```
→ [DBD] Конфиги перезагружены!
```
> Если игра в процессе — сначала `/dbd stop`, иначе новые настройки применятся только к следующей сессии.
---
### /dbd setlobby
Установить текущую позицию игрока как точку спавна лобби.
```
/dbd setlobby
```
- Сохраняется в `locations.yml`
- Эквивалентно `/dbdsetup lobby`
```
→ [DBD] Лобби установлено!
```
---
## Команды администратора — /dbdadmin
Требуют право `mdbd.admin`. Все команды только для игроков (не консоли).
---
### /dbdadmin create generator
Режим расстановки генераторов.
```
/dbdadmin create generator
```
- Включает режим: в action bar оранжевая подсказка
- **Сломай блок** — координаты сохранятся, блок не разрушится
- Повторная команда — отключает режим
- Режим остаётся активным для нескольких генераторов подряд
```
→ Режим выбора генератора включен!
→ Сломайте блок, чтобы установить генератор
(сломал блок)
→ Генератор установлен! Всего: 1
```
**Tab complete:** `generator`
---
### /dbdadmin list
Список всех установленных генераторов.
```
/dbdadmin list
```
```
=== Генераторы (3) ===
[0] game:100.00,64.00,200.00
[1] game:150.00,64.00,150.00
[2] game:200.00,64.00,100.00
```
---
### /dbdadmin remove \<id\>
Удалить генератор по ID из списка.
```
/dbdadmin remove 0
```
**Tab complete:** подсказывает существующие ID (0, 1, 2...)
```
→ Генератор удален: game:100.00,64.00,200.00
→ Генератор с ID 5 не найден (если нет такого)
```
---
### /dbdadmin clear
Удалить все генераторы.
```
/dbdadmin clear
```
> Необратимо.
```
→ Все генераторы удалены
```
---
### /dbdadmin world \<мир\>
Телепортироваться в указанный мир для настройки карты.
```
/dbdadmin world lobby
/dbdadmin world game
/dbdadmin world <любое_название_мира>
```
- `lobby` — телепорт в мир-лобби (void, платформа 7x7)
- `game` — телепорт в игровой мир
- Любое другое название — телепорт в мир с таким именем (если загружен)
```
→ Телепортирован в мир: lobby
→ Мир не найден: abc (если мир не существует)
```
**Tab complete:** `lobby`, `game`
---
## Команды настройки карты — /dbdsetup
Требуют право `mdbd.admin`.
---
### /dbdsetup wand
Выдать топорик для расстановки ворот.
```
/dbdsetup wand
```
- Выдаёт деревянный топорик (WOODEN_AXE) с подписью DBD Setup Wand
- **ЛКМ** по блоку = точка 1 (pos1)
- **ПКМ** по блоку = точка 2 (pos2)
- Только для ворот. Для остальных объектов ванд не нужен.
---
### /dbdsetup set \<тип\>
Войти в режим установки объекта. **Сломай блок** — объект установится на его месте, блок восстановится.
```
/dbdsetup set generator
/dbdsetup set hook
/dbdsetup set pallet
/dbdsetup set chest
/dbdsetup set killer-spawn
/dbdsetup set survivor-spawn
```
| Тип | Описание | Рекомендуемое кол-во |
|-----|----------|---------------------|
| `generator` | Генератор | 7 |
| `hook` | Крюк | 7–10 |
| `pallet` | Паллет | 15–20 |
| `chest` | Сундук | 5–10 |
| `killer-spawn` | Спавн киллера | 1 |
| `survivor-spawn` | Спавн выжившего | 4 |
```
→ Режим установки: hook
→ Сломай блок чтобы установить (блок восстановится)
(ломаешь блок)
→ Крюк установлен: [game, 150, 65, 200]
```
---
### /dbdsetup set gate A|B
Установить ворота через две точки топориком.
```
/dbdsetup wand — получить топорик
ЛКМ по блоку → точка 1 — нижний левый угол
ПКМ по блоку → точка 2 — верхний правый угол
/dbdsetup set gate A — установить ворота A в центре между точками
/dbdsetup set gate B — установить ворота B
```
Центр ворот вычисляется: X = средняя точка, Y = минимальная из двух.
```
→ [Wand] Точка 1: [game, 100, 64, 200]
→ [Wand] Точка 2: [game, 110, 64, 210]
→ Ворота A установлены: [game, 105, 64, 205]
```
---
### /dbdsetup build
Построить карту по шаблону и `locations.yml`.
```
/dbdsetup build
```
```
→ Строительство карты...
→ Карта построена!
```
---
### /dbdsetup clear
Очистить карту.
```
/dbdsetup clear
```
```
→ Очистка карты...
→ Карта очищена!
```
---
### /dbdsetup lobby
Установить спавн лобби на текущую позицию.
```
/dbdsetup lobby
```
Аналог `/dbd setlobby`. Сохраняется в `locations.yml`.
---
## Права доступа
| Право | Кому | Что даёт |
|-------|------|----------|
| `mdbd.player` | Все (`default: true`) | `/dbd join`, `leave`, `lobby` |
| `mdbd.admin` | OP (`default: op`) | Все остальные команды |
---
## Порядок настройки сервера
### Первый запуск
1. Запустить сервер — плагин создаст миры `lobby` и `game` автоматически
2. `/dbdadmin world lobby` — войти в лобби-мир и осмотреться (платформа 7x7)
3. `/dbd setlobby` — встать на нужную точку и сохранить спавн лобби
### Настройка игровой карты
4. `/dbdadmin world game` — перейти в игровой мир
5. Построить или загрузить карту
6. Расставить объекты:
- `/dbdsetup set generator` → ломать блоки для генераторов
- `/dbdsetup set hook` → ломать блоки для крюков
- `/dbdsetup set pallet` → ломать блоки для паллетов
- `/dbdsetup set chest` → ломать блоки для сундуков
7. Установить спавны:
- `/dbdsetup set killer-spawn` → спавн киллера
- `/dbdsetup set survivor-spawn` → 4 раза для выживших
8. Установить ворота:
- `/dbdsetup wand` → получить топорик
- ЛКМ/ПКМ по блокам → отметить углы ворот
- `/dbdsetup set gate A` и `/dbdsetup set gate B`
### Тест
8. `/dbd create` — создать сессию
9. `/dbd join` (5 игроков или меньше с форс-стартом)
10. `/dbd forcestart` — запустить не дожидаясь таймера
---
## Таблица всех команд
| Команда | Право | Описание |
|---------|-------|----------|
| `/dbd join` | player | Войти в очередь |
| `/dbd leave` | player | Покинуть сессию |
| `/dbd lobby` | player | Телепорт в лобби |
| `/dbd create` | admin | Создать сессию |
| `/dbd forcestart` | admin | Принудительный старт |
| `/dbd stop` | admin | Остановить игру |
| `/dbd reload` | admin | Перезагрузить конфиги |
| `/dbd setlobby` | admin | Сохранить спавн лобби |
| `/dbdadmin create generator` | admin | Режим расстановки генераторов |
| `/dbdadmin list` | admin | Список генераторов |
| `/dbdadmin remove <id>` | admin | Удалить генератор |
| `/dbdadmin clear` | admin | Очистить все генераторы |
| `/dbdadmin world <lobby\|game\|мир>` | admin | Телепорт в мир |
| `/dbdsetup wand` | admin | Топорик для ворот (ЛКМ/ПКМ) |
| `/dbdsetup set <тип>` | admin | Установить объект (сломать блок) |
| `/dbdsetup set gate A\|B` | admin | Установить ворота (из точек ванда) |
| `/dbdsetup build` | admin | Построить карту |
| `/dbdsetup clear` | admin | Очистить карту |
| `/dbdsetup lobby` | admin | Сохранить спавн лобби |
---
*Dead by Daylight Minecraft Plugin — актуально для версии 1.1+*

View file

@ -1,523 +0,0 @@
# DEAD BY DAYLIGHT — Minecraft Plugin
## Техническое Задание (ТЗ)
> Полная реализация механик оригинальной игры 4v1
---
## Содержание
1. [Общее описание проекта](#1-общее-описание-проекта)
2. [Распределение ролей и старт игры](#2-распределение-ролей-и-старт-игры)
3. [Механики выжившего](#3-механики-выжившего-survivor)
4. [Генераторы](#4-генераторы-generators)
5. [Механики киллера](#5-механики-киллера-killer)
6. [Паллеты и окна](#6-паллеты-pallets-и-окна-windows)
7. [Выходные ворота и люк](#7-выходные-ворота-exit-gates-и-люк-hatch)
8. [Предметы для выживших](#8-предметы-items-для-выживших)
9. [Bloodpoints и прогрессия](#9-bloodpoints-и-прогрессия)
10. [Система перков](#10-система-перков-perks)
11. [Эндгейм и итоги матча](#11-эндгейм-и-итоги-матча)
12. [Пользовательский интерфейс](#12-пользовательский-интерфейс-hud)
13. [Конфигурация](#13-конфигурация-configyml)
14. [Команды и права](#14-команды-и-права)
15. [Архитектура плагина](#15-архитектура-плагина)
16. [Приоритет разработки](#16-приоритет-разработки)
---
## 1. Общее описание проекта
### 1.1 Цель плагина
Плагин реализует полноценную мини-игру Dead by Daylight (4v1) на Minecraft-сервере. Используется готовая карта, предоставленная заказчиком. Плагин управляет всем игровым процессом: распределением ролей, механиками выживших и киллера, победными условиями, интерфейсом и звуками.
### 1.2 Технические требования
| Параметр | Значение |
|---|---|
| Платформа | Paper / Spigot 1.20.x+ |
| Язык | Java 17+ |
| Зависимости | ProtocolLib, Citizens2 (опционально), PacketEvents |
| Кол-во игроков | 5 (1 киллер + 4 выживших) |
| Карта | Предоставляется отдельно (worldname задаётся в `config.yml`) |
| Gamemode | Adventure mode на протяжении всей игры |
---
## 2. Распределение ролей и старт игры
### 2.1 Лобби и ожидание
- Лобби-мир отдельный от игровой карты.
- Минимум для старта: 5 игроков (настраивается: `min_players`, `max_players`).
- Таймер ожидания: 60 секунд после набора минимума (`countdown_seconds`).
- Если игроков < минимума — таймер сбрасывается.
- В лобби показывается статус: сколько игроков зашло / нужно.
### 2.2 Назначение ролей
- Один игрок случайно становится **Киллером (Killer)**.
- Остальные 4 становятся **Выжившими (Survivors)**.
- Реализовать систему очереди: если игрок давно не был киллером — шанс повышается.
- Роли объявляются через Title + Subtitle (красный текст для киллера, белый для выжившего).
- Роль киллера не раскрывается другим выжившим до спавна (5-секундная задержка перед телепортом киллера).
### 2.3 Спавн
- Спавн-точки прописаны в `config.yml` (4 точки для выживших + 1 для киллера).
- Выжившие телепортируются одновременно.
- Киллер телепортируется через **5 секунд** после выживших (grace period).
- Во время grace period киллер видит таймер в ActionBar.
- Генераторы, хуки, выходы и другие объекты расставляются при старте игры по координатам из `config.yml`.
> ⚠ Все координаты (спавны, хуки, генераторы, выходы, подвал) задаются в `config.yml` — не хардкодятся в коде.
---
## 3. Механики выжившего (Survivor)
### 3.1 Состояния здоровья
Выживший имеет три состояния — переходы строго последовательные:
| Состояние | Описание | Визуал в Minecraft |
|---|---|---|
| **Healthy** (здоров) | Может бежать, прыгать, взаимодействовать | Полное здоровье, без эффектов |
| **Injured** (ранен) | Оставляет следы крови, издаёт стоны, скорость чуть снижена | Частицы крови, звук боли каждые 3 сек |
| **Downed** (повален) | Ползёт по полу, не может бежать или взаимодействовать | Crawl-анимация (Sneak + замедление), скорость 30% |
| **Hooked** (на крюке) | Привязан к крюку, ждёт спасения | Заморожен, анимация качания на крюке |
| **Dead** (мёртв) | Выбывает из игры | Spectator mode |
### 3.2 Переходы состояний
- **Healthy → Injured:** одно попадание от киллера.
- **Injured → Downed:** второе попадание от киллера.
- **Downed → Hooked:** киллер подбирает (ПКМ) и несёт к крюку.
- **Hooked → Dead:** истекло 2 стадии крюка, никто не спас.
- **Injured → Healthy:** использование аптечки или спасение с крюка.
### 3.3 Передвижение
- **Healthy:** обычная скорость + возможность спринта.
- **Injured:** скорость снижена на 5% (speed effect).
- **Downed:** только ползти — отрицательный Speed, принудительный Sneak, без прыжков.
- **Спринт (Exhaustion):** можно спринтовать конечное время (`exhaustion_seconds`, по умолчанию 3 сек спринта, затем 5 сек кулдауна).
> ⚠ Спринт — ключевая механика для чейса. Реализовать через `isSprinting()` + кастомный кулдаун.
### 3.4 Исцеление (Healing)
- **Аптечка (Med-Kit):** зажать ПКМ 10 секунд, прогресс-бар в ActionBar.
- **Исцеление другим выжившим:** подойти вплотную, зажать ПКМ 16 секунд (медленнее, чем аптечкой).
- Если движение прервалось (игрок отошёл, получил удар) — прогресс сбрасывается.
- Иконка прогресса в ActionBar: `[████░░░░░░] Исцеление 40%`
### 3.5 Механика крюка (Hook)
После того как киллер повесил выжившего на крюк:
| Стадия | Длительность | Событие |
|---|---|---|
| Stage 1 | 60 секунд | Выживший висит. Можно спасти. В ActionBar — таймер. |
| Stage 1 → 2 | Автоматически | Если не спасли за 60 сек — переход на Stage 2. |
| Stage 2 | 60 секунд | Выживший борется с Сущностью. Каждые 2 сек — Skill Check (QTE). |
| Stage 2 провален | Мгновенно | Выживший умирает — переход в Spectator. |
| Спасение с крюка | 2 секунды | Другой выживший снимает с крюка (прогресс-бар). Стадия запоминается. |
- Один и тот же выживший может быть повешен максимум **3 раза** (`hook_count`). На 3-й раз — мгновенная смерть.
- Крюки в подвале (Basement) не уничтожаются и дают бонусные Bloodpoints.
> ⚠ Skill Check на Stage 2 — случайное появление индикатора в Title. Игрок должен нажать Shift или Drop в нужный момент. Промах — мгновенная смерть.
### 3.6 Спасение с крюка
- Выживший подходит к крюку с союзником на расстояние 2 блока.
- Зажимает ПКМ (или Sneak) в течение 2 секунд — прогресс в ActionBar.
- Спасённый получает статус Injured (или остаётся Injured).
- Спасатель не получает защиты — может быть немедленно атакован.
- **Borrowed Time:** если киллер стоит рядом с крюком в момент спасения — спасённый получает 10 сек защиты Endurance (не переходит в Downed с первого удара).
### 3.7 Ползание (Dying State)
- Повалённый выживший ползёт со скоростью 30% от нормальной.
- Может ползти к другому выжившему — тот может его поднять (Recovery, 4 секунды).
- После Recovery выживший встаёт в статусе Injured.
- Самовосстановление невозможно без специального перка.
- Повалённый выживший издаёт стоны (слышимые киллеру).
---
## 4. Генераторы (Generators)
### 4.1 Общие правила
- На карте спавнится **7 генераторов**, нужно починить **5**.
- Количество настраивается: `total_generators`, `required_generators` в `config.yml`.
- Починённые генераторы светятся (частицы + звук).
- Киллер видит ауру всех непочиненных генераторов (через частицы или Sidebar).
### 4.2 Процесс починки
- Выживший подходит к генератору и зажимает ПКМ — начинается починка.
- Прогресс отображается в ActionBar.
- Базовое время починки: **80 секунд** (один выживший).
- Два выжившего на генераторе: скорость ×1.5 (`gen_coop_multiplier`).
- Три и более: скорость ×2.0 (ограничено).
- Если выживший отошёл или получил удар — прогресс частично откатывается (regression).
### 4.3 Skill Checks (Проверки навыка)
- В Title показывается круговой индикатор (симулируется через Title-анимации).
- Игрок должен нажать **Drop (Q)** в нужный момент (попасть в зелёную зону).
- **Успешный Skill Check:** +5% к прогрессу генератора + бонусные Bloodpoints.
- **Провальный Skill Check:** взрыв — генератор теряет 10% прогресса, громкий звук (слышен киллеру в радиусе 40 блоков).
- Частота skill check: раз в 20–40 секунд (рандомно).
> ⚠ Звук провального skill check — важнейший инструмент для киллера. Реализовать через `playSound()` с большим радиусом.
### 4.4 Регрессия генератора
- Если все выжившие отошли от работающего генератора — он начинает откатываться (0.25% в секунду).
- Киллер может ударить по генератору (ПКМ) — **Kick:** мощная регрессия 2.5% в секунду на 30 секунд.
- Регрессию можно остановить — выживший подходит и нажимает ПКМ.
- Взорванный генератор издаёт звук и выбрасывает частицы взрыва.
---
## 5. Механики киллера (Killer)
### 5.1 Базовые характеристики
| Параметр | Значение | Настройка |
|---|---|---|
| Скорость | 115% от скорости выжившего | `killer_speed` |
| Terror Radius | 32 блока | `terror_radius` |
| Урон атаки | Переводит в Injured / Downed | Не числовой — по состоянию |
| Кулдаун атаки | 2.5 секунды | `attack_cooldown` |
| Перенос выжившего | 92% скорости | `carry_speed` |
### 5.2 Terror Radius и Red Stain
- **Terror Radius** — зона 32 блока вокруг киллера. Выжившие в этой зоне слышат сердцебиение.
- Интенсивность звука увеличивается при сближении (3 уровня: далеко / близко / рядом).
- **Red Stain** — красное свечение на земле за киллером (частицы). Показывает направление взгляда.
- Реализация Red Stain: каждые 0.5 сек спавнить Dust-частицы красного цвета позади головы киллера.
### 5.3 Атака и удар
- **Базовая атака:** ЛКМ по выжившему. Урон по состояниям (Healthy→Injured или Injured→Downed).
- После удара — кулдаун 2.5 сек (замедление на 0.5 сек).
- Нельзя атаковать сквозь окна / паллеты (проверка LineOfSight).
- **Bloodlust:** если киллер преследует выжившего >15 секунд без попадания — бонус скорости:
- Тир 1: +3%
- Тир 2: +5%
- Тир 3: +8%
- Тиры сбрасываются при ударе или смене цели.
### 5.4 Подъём и перенос выжившего
- Когда выживший Downed — киллер нажимает ПКМ для подъёма (2.5 сек анимации).
- Во время переноса: киллер замедлен до 92% скорости, не может атаковать.
- Выживший сидит на киллере (через ArmorStand или Mount).
- Чтобы повесить: киллер подходит к хуку и нажимает ПКМ.
- **Wiggle (вырывание):** выживший нажимает Sneak/Jump быстро — прогресс побега в ActionBar. При 100% — выживший падает рядом в Downed статусе.
- Время вырывания: **16 секунд** (`wiggle_duration`).
### 5.5 Взаимодействие с генераторами
- Киллер нажимает ПКМ на генератор — **Kick** (регрессия, см. раздел 4.4).
- Киллер видит ауру всех незаконченных генераторов в Sidebar (иконки + расстояние).
### 5.6 Подвал (Basement)
- Специальная зона на карте с 4 крюками.
- Крюки в подвале **нельзя саботировать**.
- Перевод выжившего в подвал засчитывается быстрее + бонус Bloodpoints.
---
## 6. Паллеты (Pallets) и Окна (Windows)
### 6.1 Паллеты
- Паллеты — деревянные барьеры на карте (блоки, отмеченные в конфиге).
- Выживший подходит к паллете и нажимает ПКМ — паллета падает (standing → dropped).
- Если киллер коснулся упавшей паллеты — **Stun 2.5 сек** (звук, частицы).
- Киллер может **сломать паллету**: зажать ПКМ на 3 секунды. Паллета исчезает навсегда.
- Количество паллет на карте конечно. Статус каждой хранится в памяти.
- Выживший может пробежать через упавшую паллету (Vault, 0.6 сек).
### 6.2 Окна (Windows)
- Окна — прыжковые зоны в стенах карты (координаты в конфиге).
- Выживший запрыгивает в окно: **1 секунда** анимации Vault (замедление → ускорение).
- Киллер тоже может прыгнуть в окно, но медленнее (1.5 сек).
- **Fake Vault (Mindgame):** игрок может зайти в начало вота и выйти обратно.
> ⚠ Паллеты и окна — основа loop-механики чейса. Без правильного расположения на карте игра будет несбалансированной.
---
## 7. Выходные ворота (Exit Gates) и Люк (Hatch)
### 7.1 Выходные ворота
- После починки 5 генераторов активируются **2 выходных ворот** (`gate_locations`).
- Рубильник ворот загорается (смена блока, звук).
- Выживший взаимодействует с рубильником **20 секунд** (прогресс в ActionBar).
- Если прервать — прогресс **не откатывается** (в отличие от генераторов).
- Открытые ворота остаются открытыми навсегда.
- Выживший пересекает границу выхода → **ESCAPED!** (Title).
- **Эндгейм-коллапс:** через 2 минуты после открытия первых ворот — все оставшиеся выжившие умирают.
### 7.2 Люк (Hatch)
- Люк появляется, когда выживших меньше, чем непочиненных генераторов.
- Правило: если X выживших живы и < X генераторов починено — люк открыт.
- Последний выживший **всегда** видит открытый люк.
- Координата люка — `hatch_location` в конфиге.
- Прыжок в люк = Escape (побег через люк).
- Киллер может **закрыть люк** (ПКМ). После этого активируется End Game Collapse.
- Закрытый люк можно открыть выжившим с **Ключом (Key)** за 2.5 секунды.
---
## 8. Предметы (Items) для выживших
Предметы выдаются игрокам перед игрой или находятся в Сундуках (Chests) на карте. Максимум **1 предмет** в руках.
| Предмет | Действие | MC-предмет | Ограничение |
|---|---|---|---|
| **Med-Kit** | Лечит Injured → Healthy за 10 сек | Red Dye / Custom | 1 заряд |
| **Flashlight** | Слепит киллера (Blindness 2 сек) | Glowstone Dust / Custom | 8 использований |
| **Toolbox** | Ускоряет починку гена на 30% | Iron Pickaxe / Custom | 1 генератор |
| **Key** | Открывает закрытый люк | Gold Nugget / Custom | 1 использование |
| **Map** | Показывает ауру генераторов выжившим | Paper / Custom | Пассивно |
### 8.1 Сундуки
- На карте расставлено N сундуков (`chest_locations` в конфиге).
- Каждый сундук при открытии выдаёт случайный предмет (весовая таблица в `loot_table.yml`).
- Открытие: **3 секунды** (прогресс-бар). Один сундук — один предмет навсегда.
- Киллер может видеть ауры сундуков (опционально, через перк).
---
## 9. Bloodpoints и прогрессия
Bloodpoints — внутриигровая валюта, начисляется за действия в раунде. Хранятся в базе данных (SQLite или YAML).
| Действие | Выживший (BP) | Киллер (BP) |
|---|---|---|
| Починить генератор (полностью) | +1000 | — |
| Успешный Skill Check | +150 | — |
| Спасти с крюка | +800 | — |
| Исцелить союзника | +600 | — |
| Сбежать | +5000 | — |
| Повесить выжившего на крюк | — | +800 |
| Повалить выжившего | — | +500 |
| Пожертвовать выжившего (3 стадии) | — | +2000 |
| Сломать паллету | — | +200 |
| Ударить по генератору (Kick) | — | +100 |
- Bloodpoints отображаются в Title после окончания матча.
- Статистика сохраняется в базе данных.
---
## 10. Система перков (Perks)
Перки — пассивные и активные способности. Каждый игрок берёт **4 перка** перед матчем (в GUI-меню выбора).
### 10.1 Перки Выживших (минимум 10)
| Перк | Эффект |
|---|---|
| **Self-Care** | Позволяет лечить себя без аптечки (вдвое медленнее) |
| **Dead Hard** | При нажатии Sneak во время спринта — кратковременный рывок (0.5 сек неуязвимости) |
| **Borrowed Time** | После спасения с крюка — 10 сек защита Endurance |
| **Sprint Burst** | При начале спринта — ускорение ×1.5 на 3 сек (кулдаун 40 сек) |
| **Iron Will** | Убирает звуки стонов в Injured состоянии |
| **Spine Chill** | Предупреждение (иконка + звук), если киллер смотрит на выжившего с 36 блоков |
| **Resilience** | В Injured/Downed — скорость починки +9%, скорость вота +9% |
| **Kindred** | После повешения на крюк — все союзники видят ауру киллера |
| **Prove Thyself** | За каждого выжившего на генераторе — скорость +15% (стакается) |
| **Adrenaline** | При открытии 5-го генератора — мгновенно Healthy + Sprint Burst 5 сек |
### 10.2 Перки Киллера (минимум 10)
| Перк | Эффект |
|---|---|
| **Hex: Ruin** | Все генераторы регрессируют вдвое быстрее (пока горит тотем) |
| **Corrupt Intervention** | 3 самых дальних генератора заблокированы 2 минуты в начале игры |
| **Pop Goes the Weasel** | После повешения — следующий Kick откатывает 25% прогресса гена |
| **Deadlock** | После починки любого гена — самый прогрессивный ген блокируется на 30 сек |
| **No Way Out** | После повешения — добавляется задержка при открытии ворот (+12/24/36 сек) |
| **Scourge Hook: Pain Resonance** | Повешение на Scourge Hook взрывает ближайший ген (−25%) |
| **Nowhere to Hide** | После Kick гена — ауры выживших видны 4 сек в радиусе 24 блоков |
| **Lethal Pursuer** | В начале игры — видеть ауры всех выживших 7 секунд |
| **BBQ & Chili** | После повешения — видеть ауры выживших >40 блоков на 4 сек |
| **Tinkerer** | Когда ген достигает 70% — уведомление + Undetectable на 12 сек |
> ⚠ Перки реализовать через EventListener. Каждый перк — отдельный класс, реализующий интерфейс `IPerk` с методами `onEquip()`, `onTrigger()`, `onRemove()`.
### 10.3 Тотемы (Totems)
- На карте **5 тотемов** (`totem_locations` в конфиге).
- **Dull Totem:** выживший ломает за 14 секунд — начисляются Bloodpoints.
- **Hex Totem:** активируется при использовании Hex-перка. Пока горит — перк активен.
- При уничтожении Hex Totema — связанный перк деактивируется навсегда.
---
## 11. Эндгейм и итоги матча
### 11.1 Условия победы
| Сторона | Победа | Поражение |
|---|---|---|
| **Выжившие** | Минимум 2 выживших сбежали (ворота или люк) | Все выжившие мертвы до побега |
| **Киллер** | Все 4 выжившие принесены в жертву / умерли | 2+ выжившие сбежали |
### 11.2 Экран результатов
- Все игроки телепортируются в лобби.
- В Chat выводится таблица: игрок, роль, Bloodpoints, статус (Escaped / Sacrificed).
- Title на 5 секунд: **ПОБЕДА / ПОРАЖЕНИЕ**.
- Личная статистика матча: генераторы, спасения, хуки, урон.
### 11.3 Эндгейм Коллапс (End Game Collapse)
- Активируется при: открытии ворот ИЛИ закрытии люка.
- Таймер **120 секунд** показывается всем в ActionBar красным текстом.
- По истечении — все оставшиеся выжившие мгновенно умирают.
---
## 12. Пользовательский интерфейс (HUD)
### 12.1 ActionBar
- Постоянно: статус игрока (`❤ Healthy` / `🩸 Injured` / `💀 Downed`).
- При взаимодействии: прогресс-бар починки / лечения / спасения.
- Эндгейм Коллапс: красный таймер обратного отсчёта.
### 12.2 Sidebar (Scoreboard)
- **Выжившие:** количество починенных генераторов (`⚡ 2/5`), сколько выживших живо (`👥 3/4`).
- **Киллер:** сколько выживших на крюке / мертво, прогресс генераторов.
### 12.3 Title / Subtitle
- Оповещения: генератор починен, выживший на крюке, ворота открыты.
- Начало игры: роль + краткая инструкция.
- Skill Check: вспышка в Title с таймером.
### 12.4 Звуки
- **Terror Radius:** нарастающее сердцебиение по мере приближения киллера (3 уровня).
- **Генератор:** звук работы при починке, взрыв при провале skill check или Kick.
- **Спасение с крюка:** звук успеха.
- **Люк:** звук открытия / закрытия.
- **Ворота:** звук активации, гудение.
> ⚠ Рекомендуется базовый Resource Pack для кастомных звуков. Без него — стандартные MC-звуки как замена.
---
## 13. Конфигурация (config.yml)
| Секция | Ключ | Тип | По умолчанию |
|---|---|---|---|
| `game` | `min_players` | int | 5 |
| `game` | `max_players` | int | 5 |
| `game` | `countdown_seconds` | int | 60 |
| `game` | `world_name` | string | `dbd_map` |
| `game` | `lobby_world` | string | `lobby` |
| `killer` | `killer_speed` | double | 1.15 |
| `killer` | `terror_radius` | int | 32 |
| `killer` | `attack_cooldown` | double | 2.5 |
| `killer` | `carry_speed` | double | 0.92 |
| `killer` | `wiggle_duration` | double | 16.0 |
| `survivor` | `sprint_duration` | double | 3.0 |
| `survivor` | `sprint_cooldown` | double | 5.0 |
| `survivor` | `heal_self_time` | double | 10.0 |
| `survivor` | `heal_other_time` | double | 16.0 |
| `generator` | `total_generators` | int | 7 |
| `generator` | `required_generators` | int | 5 |
| `generator` | `repair_time` | double | 80.0 |
| `generator` | `gen_coop_multiplier` | double | 1.5 |
| `generator` | `regression_rate` | double | 0.25 |
| `hook` | `stage1_duration` | int | 60 |
| `hook` | `stage2_duration` | int | 60 |
| `hook` | `max_hook_count` | int | 3 |
| `endgame` | `collapse_duration` | int | 120 |
**Дополнительные файлы конфигурации:**
- `locations.yml` — все спавн-точки, координаты генераторов, хуков, тотемов, сундуков, ворот, люка.
- `loot_table.yml` — весовая таблица предметов в сундуках.
- `perks.yml` — настройки перков (кулдауны, длительности).
---
## 14. Команды и права
| Команда | Описание | Право |
|---|---|---|
| `/dbd join` | Войти в очередь на игру | `dbd.play` |
| `/dbd leave` | Выйти из очереди / матча | `dbd.play` |
| `/dbd forcestart` | Принудительно запустить игру | `dbd.admin` |
| `/dbd stop` | Принудительно остановить игру | `dbd.admin` |
| `/dbd setkiller <игрок>` | Назначить игрока киллером | `dbd.admin` |
| `/dbd setspawn <killer\|survivor1..4>` | Установить спавн-точку (по курсору) | `dbd.admin` |
| `/dbd setgen <id>` | Отметить блок как генератор | `dbd.admin` |
| `/dbd sethook <id>` | Отметить блок как крюк | `dbd.admin` |
| `/dbd setgate <1\|2>` | Отметить зону ворот | `dbd.admin` |
| `/dbd sethatch` | Отметить блок как люк | `dbd.admin` |
| `/dbd reload` | Перезагрузить конфиг | `dbd.admin` |
| `/dbd stats [игрок]` | Посмотреть статистику | `dbd.play` |
---
## 15. Архитектура плагина
### 15.1 Структура пакетов
| Пакет | Назначение |
|---|---|
| `com.dbd.plugin.core` | Главный класс, GameManager, EventBus |
| `com.dbd.plugin.game` | GameSession, GameState, RoleAssigner, RoundTimer |
| `com.dbd.plugin.roles` | KillerController, SurvivorController, PlayerState |
| `com.dbd.plugin.mechanics` | GeneratorManager, HookManager, PalletManager, HatchManager, GateManager |
| `com.dbd.plugin.perks` | IPerk, PerkRegistry, PerkLoader, [все перки] |
| `com.dbd.plugin.items` | IItem, ItemRegistry, ChestManager |
| `com.dbd.plugin.ui` | HUDManager, ActionBarManager, TitleManager, SoundManager |
| `com.dbd.plugin.data` | DatabaseManager, PlayerStats, LocationsConfig |
| `com.dbd.plugin.commands` | DBDCommand, TabCompleter |
### 15.2 Ключевые принципы
- Каждый раунд — отдельный объект `GameSession` со своим состоянием.
- Все механики управляются через `BukkitScheduler` (tick-based системы).
- Перки реализованы через Event-Driven подход (не хардкод в механиках).
- Конфиги не перезаписывают пользовательские настройки при обновлении (`saveDefaultConfig`).
- Логирование всех событий в отдельный лог-файл (`logs/dbd.log`).
> ⚠ Плагин должен корректно обрабатывать disconnect игрока во время матча — замена ботом или перераспределение ролей.
---
## 16. Приоритет разработки
| Этап | Функционал | Статус |
|---|---|---|
| **MVP (Этап 1)** | Старт/стоп игры, роли, спавн, базовые состояния выжившего, атака киллера | 🔴 В разработке |
| **Этап 2** | Генераторы (починка, прогресс, регрессия, skill check), хуки (все стадии) | 🔴 В разработке |
| **Этап 3** | Паллеты, окна, перенос, вырывание, люк, ворота, эндгейм | 🔴 В разработке |
| **Этап 4** | Предметы, сундуки, тотемы, базовые перки (10 survivor + 10 killer) | 🔴 В разработке |
| **Этап 5** | Полный HUD, звуки, Terror Radius, Red Stain, Bloodpoints, база данных | 🔴 В разработке |
| **Этап 6** | Балансировка, все остальные перки, тест-раунды, оптимизация | 🔴 В разработке |
---
*ТЗ подготовлено с учётом всех оригинальных механик Dead by Daylight (4v1) | Версия 1.0*

392
PLAN.md
View file

@ -1,392 +0,0 @@
# DBD Plugin — Баги и задачи
## ✅ Закрыто в текущей сессии
### SetupCommand (упрощено)
- [x] `/dbdsetup set <тип>` — ломаешь блок, объект ставится (без `select`)
- [x] Ванд только для ворот: ЛКМ=pos1, ПКМ=pos2
- [x] `/dbdsetup set gate A|B` — установить ворота из точек ванда
### Медкит
- [x] Требует Shift + ПКМ + стоять на месте (прерывается при движении или отпускании Shift)
- [x] После использования удаляется полностью (1 медкит = 1 лечение)
- [x] Медкит не выдаётся стартово — только из сундуков
### Маньяк — атака
- [x] Post-hit cooldown 4 сек после ПОПАДАНИЯ (отдельно от swing cooldown 2.5 сек)
- [x] Параметр `killer.post-hit-cooldown` в `game.yml`
### Маньяк — нокдаун / переноска
- [x] Игрок в нокдауне ложится (`setSwimming(true)`) и поза удерживается каждые 5 тиков
- [x] При подъёме, смерти, спасении — поза сбрасывается
- [x] Переноска: маньяк ПКМ → подбирает DOWNED в радиусе 3 блоков
- [x] При подъёме `setSwimming(false)` отменяет позу нокдауна
### Таймер игры
- [x] 15 минут + BossBar с таймером для всех игроков (зелёный → жёлтый → красный)
- [x] По истечении — маньяк победил
### После победы
- [x] Телепортирует всех в лобби
- [x] Scale сбрасывается до 1.0
- [x] Инвентарь очищается
### Лобби
- [x] Лимит 1 лобби на игрока
- [x] Broadcast при создании лобби убран — сообщение только создателю
- [x] Сообщение о входе игрока — только участникам сессии (не всему серверу)
- [x] После завершения игры лобби пересоздаёт сессию автоматически (onSessionFinished callback)
- [x] Лобби не засоряются — реестр чистит сессию через `GameSession.setOnStopped()`
### PlayerQuitListener
- [x] Счётчик живых выживших корректный (считает после пометки DEAD)
- [x] Инвентарь, scale, поза сбрасываются при выходе из игры
### GameplayPhase
- [x] Разбит на части:
- `GameplayPhase.java` — только lifecycle (enter/tick/exit) + делегирование (230 строк)
- `GameplayHookHandler.java` — тик крюков, Stage 2, Struggle, Sacrifice (87 строк)
- `GameplayWinConditions.java` — проверка победы/поражения, SPECTATOR (91 строка)
- `GameplayDamageSystem.java` — система урона, атаки, предметы (155 строк)
- `GameplayUI.java` — BossBar, sidebar, таймер (142 строки)
- `GameplayInteraction.java` — обработка взаимодействий (198 строк)
### Лобби — повторный вход
- [x] **FIX**: Исправлен `GameManager.joinSession()` — теперь корректно обрабатывает повторный вход, сначала удаляя игрока из старой сессии
### Хотбар маньяка
- [x] **FIX**: Добавлена проверка `!bp.isKiller()` в `ChestOpening.completeOpening()` — маньяк не получает лут из сундуков
### Медкит из сундука
- [x] **WORKS**: Логика уже работает — `ChestLoot.rollLoot()` выдаёт медкит/фонарик при открытии сундука
### Счётчик выживших
- [x] **VERIFIED**: Логика в `PlayerQuitListener.handleSurvivorDisconnect()` корректная — считает после пометки DEAD
### Ворота
- [x] **FIX**: Добавлена интеграция `GateOpening` в `GameplayInteraction` — теперь вызывается при взаимодействии с порталом (HOPPER/PORTAL)
### Кодировка и спавн в воздухе
- [x] **FIXED**: UTF-8 encoding для build
- Добавлена `org.gradle.jvmargs=-Dfile.encoding=UTF-8` в `gradle.properties`
- Добавлен `tasks.withType<ProcessResources> { filteringCharset = "UTF-8" }` в build.gradle.kts
- Русские символы и эмодзи теперь сохраняются корректно в конфигах
- [x] **FIXED**: Спавн в воздухе
- Добавлена корректировка Y-координаты (`Math.floor(y) + 1.0`) в `RoleAssigner.java`
- Players теперь стоят ОН block, не ИН block
- Работает для положительных и отрицательных Y (подземные карты)
- [x] **FIXED**: World loading — добавлен fallback к первому loaded world в `LocationsConfig.java`
- [x] **FIXED**: Удален неиспользуемый импорт Location из `PalletBreak.java`
---
## ✅ Завершено
### Заполнение сундуков при старте игры (случайный лут из ChestLoot)
- [x] Лут записывается в поле `Chest.loot` при создании
- [x] `ChestOpening.completeOpening()` использует pre-set loot
### Визуализация ворот (частицы при открытии)
- [x] Progress particles: CRIT every 5 ticks
- [x] Completion burst: EXPLOSION_EMITTER + FLAME
### Удаление перков и тотемов
- [x] Все totem файлы удалены
- [x] Все ссылки на тотемы удалены
## ❌ Новые баги (текущая сессия)
### ✅ ИСПРАВЛЕНО (7 багов)
1. **SetupCommand MiniMessage формат**
- [x] **FIXED**: Заменены legacy `§f` и `§a` на MiniMessage `<#FFFFFF>` и `<#00FF00>`
2. **Телепортация в конце игры не работает**
- [x] **FIXED**: Добавлена телепортация в `GameplayPhase.exit()`
- Также сбрасывается scale до 1.0
3. **Downed игрок берёт хук**
- [x] **FIXED**: Добавлена проверка состояния в `GameplayInteraction`
- Теперь DOWNED, CARRIED и HOOKED не могут спасать
4. **Генераторы не чинятся за игрока**
- [x] **FIXED**: Подключена `GeneratorRepair` в `GameplayPhase`
- Добавлен обработчик в `GameplayInteraction`
- Механика: ПКМ по генератору → зажать ПКМ для починки
---
### ✅ ИСПРАВЛЕНО / ПРОВЕРЕНО В ТЕКУЩЕЙ СЕССИИ (13 ЗАДАЧ)
1. **Генераторы (#11) — generatorManager всегда null**
- [x] **FIXED**: Переинициализирован в GameplayPhase.initSubHandlers()
2. **Телепорт (#10) — ранний выход отменял финал**
- [x] **FIXED**: Удалён телепорт из GameplayPhase.exit()
- Телепортация только в EndgamePhase.exit() (100L тиков задержка)
3. **Wand для ворот — STICK vs WOODEN_AXE**
- [x] **FIXED**: Обновлена документация, код ищет WOODEN_AXE с NBT тегом
4. **Бесконечный вход в лобби** (/dbd join X, /dbd join X, /dbd join X...)
- [x] **FIXED**: LobbyRegistry.joinLobby() проверяет текущую сессию
- Если игрок уже в другой сессии → выходит оттуда перед входом
5. **Лобби удаляется при 0 игроков, tab-complete показывает**
- [x] **VERIFIED**: onPlayerLeftSession() НЕ удаляет лобби из реестра
- Лобби держится в памяти, session переиспользуется после игры
6. **Pallet setup** (/dbdsetup set pallet)
- [x] **VERIFIED**: Режим selectingPallet работает, сохраняет через SpawnUtil.blockBreakSpawn()
7. **Chest setup** (/dbdsetup set chest)
- [x] **VERIFIED**: Режим selectingChest работает, сохраняет с offset +0.5, +0, +0.5
8. **Эффекты ранения** (нет визуалов когда игрок INJURED)
- [x] **VERIFIED**: GameplayDamageSystem добавляет:
- Частицы HEART каждые 40 тиков
- Звук при попадании удара
- Title "РАНЕН!"
9. **Двойной sneak для pickup** (киллер присел 2 раза возле DOWNED)
- [x] **VERIFIED**: GameplayInteraction проверяет интервал <450ms между sneaks
- Вызывает tryPickup() из KillerController
10. **Админ-команды** (heal, down, unhook, state, hooks, kill)
- [x] **VERIFIED**: DBDAdminPlayerCommand полностью реализована
- /dbd admin heal [игрок]
- /dbd admin down [игрок]
- /dbd admin unhook [игрок]
- /dbd admin state <игрок> <HEALTHY|INJURED|DOWNED|...>
- /dbd admin hooks <игрок> <0-3>
- /dbd admin kill [игрок]
11. **Телепорт после завершения игры в лобби**
- [x] **VERIFIED**: EndgamePhase.exit() телепортирует через 100L тиков
- Сбрасывает scale до 1.0, очищает инвентарь
12. **Генераторы не чинятся** (ПКМ возле генератора)
- [x] **FIXED**: setOnGeneratorInteract() вызывает generatorRepair.startRepair()
- generatorManager переинициализирован, не null
13. **HOOKED игрок берёт хук** (игрок висит на крюке, может взять другой)
- [x] **VERIFIED**: setOnRescueAttempt() проверяет HOOKED, DOWNED, CARRIED
- Rescue невозможен если rescuer в одном из этих статусов
14. **Спавн игроков в воздухе** (проблема с высотой)
- [x] **FIXED**: SpawnUtil.resolveStandingLocation() улучшена логика расчета Y
- Добавлена явная загрузка chunks в RoleAssigner перед спавном
- Используется getHighestBlockYAt() + 1 для стояния на блоке
15. **GameMode.ADVENTURE при начале игры**
- [x] **FIXED**:
- RoleAssigner.assignRoles() устанавливает ADVENTURE при спавне
- LobbyPhase.enter() и onPlayerJoined() устанавливают ADVENTURE
- EndgamePhase.exit() сбрасывает на ADVENTURE перед лобби
16. **/dbdsetup set gate A confirm не работал**
- [x] **FIXED**: Добавлена поддержка обоих форматов:
- `/dbdsetup set gate A confirm`
- `/dbdsetup set gate confirm A`
- Обновлена документация в COMMANDS_GUIDE.md
17. **Переноска киллером (carry) — проблемы**
- [x] **FIXED**: Переработана логика KillerCarry с использованием ArmorStand:
- Игрок **видим** (не setInvisible)
- Камера **отделена** (beide мають пассажиры armor stand)
- Игрок **не двигает** киллера (mounted, не телепортируется)
- Пробел **работает** (jump event отслеживается)
---
### ✅ ИТОГО: 17/17 ЗАДАЧ РЕШЕНО!1. **Генераторы (#11) — generatorManager всегда null**
- [x] **FIXED**: Переинициализирован в GameplayPhase.initSubHandlers()
2. **Телепорт (#10) — ранний выход отменял финал**
- [x] **FIXED**: Удалён телепорт из GameplayPhase.exit()
- Телепортация только в EndgamePhase.exit() (100L тиков задержка)
3. **Wand для ворот — STICK vs WOODEN_AXE**
- [x] **FIXED**: Обновлена документация, код ищет WOODEN_AXE с NBT тегом
4. **Бесконечный вход в лобби** (/dbd join X, /dbd join X, /dbd join X...)
- [x] **FIXED**: LobbyRegistry.joinLobby() проверяет текущую сессию
- Если игрок уже в другой сессии → выходит оттуда перед входом в новую
5. **Лобби удаляется при 0 игроков, tab-complete показывает**
- [x] **VERIFIED**: onPlayerLeftSession() НЕ удаляет лобби из реестра
- Лобби держится в памяти, session переиспользуется после игры
6. **Pallet setup** (/dbdsetup set pallet)
- [x] **VERIFIED**: Режим selectingPallet работает, сохраняет через SpawnUtil.blockBreakSpawn()
7. **Chest setup** (/dbdsetup set chest)
- [x] **VERIFIED**: Режим selectingChest работает, сохраняет с offset +0.5, +0, +0.5
8. **Эффекты ранения** (нет визуалов когда игрок INJURED)
- [x] **VERIFIED**: GameplayDamageSystem добавляет:
- Частицы HEART каждые 40 тиков
- Звук при попадании удара
- Title "РАНЕН!"
9. **Двойной sneak для pickup** (киллер присел 2 раза возле DOWNED)
- [x] **VERIFIED**: GameplayInteraction проверяет интервал <450ms между sneaks
- Вызывает tryPickup() из KillerController
10. **Админ-команды** (heal, down, unhook, state, hooks, kill)
- [x] **VERIFIED**: DBDAdminPlayerCommand полностью реализована
- /dbd admin heal [игрок]
- /dbd admin down [игрок]
- /dbd admin unhook [игрок]
- /dbd admin state <игрок> <HEALTHY|INJURED|DOWNED|...>
- /dbd admin hooks <игрок> <0-3>
- /dbd admin kill [игрок]
11. **Телепорт после завершения игры в лобби**
- [x] **VERIFIED**: EndgamePhase.exit() телепортирует через 100L тиков
- Сбрасывает scale до 1.0, очищает инвентарь
12. **Генераторы не чинятся** (ПКМ возле генератора)
- [x] **FIXED**: setOnGeneratorInteract() вызывает generatorRepair.startRepair()
- generatorManager переинициализирован, не null
13. **HOOKED игрок берёт хук** (игрок висит на крюке, может взять другой)
- [x] **VERIFIED**: setOnRescueAttempt() проверяет HOOKED, DOWNED, CARRIED
- Rescue невозможен если rescuer в одном из этих статусов
14. **Спавн игроков в воздухе** (проблема с высотой)
- [x] **FIXED**:
- SpawnUtil.resolveStandingLocation() улучшена логика расчета Y
- Добавлена явная загрузка chunks в RoleAssigner перед спавном
- Используется getHighestBlockYAt() + 1 для стояния на блоке
---
### ✅ ИТОГО: 14/14 ЗАДАЧ РЕШЕНО!
---
## ✅ СЕССИЯ 2: ГЕНЕРАТОР - ПРОГРЕСС И КРАУЧ (18-19 ЗАДАЧИ)
### 18. Прогресс починки генератора не сохраняется
- [x] **FIXED**: Progress теперь сохраняется в Generator объекте между repair attempts
- Механика:
- Игрок присаживается рядом генератором → `onSneak()` автоматически запускает `startRepair()`
- `startRepair()` проверяет, ремонтирует ли уже этот генератор → если нет, создаёт новую RepairSession
- Progress сохраняется в `generator.progress` на всё время игры
- Если игрок встал (un-sneak) → `updateRepair()` возвращает false → repair stops
- Если игрок присел снова рядом тем же генератором → вызывается `startRepair()` снова
- **Ключевое**: progress НЕ сбрасывается, потому что RepairSession.generator указывает на ТОТЖЕ Generator объект
- Progress сохраняется всю игру (сбрасывается только при перезагрузке сервера)
- Сообщение теперь показывает текущий прогресс: "Зажмите ПРИСЕД чтобы чинить генератор (прогресс: XX%)"
### 19. Чтобы починить генератор нужно присеть возле него и быть присядя возле него
- [x] **FIXED**: Добавлена автоматическая триггеризация repair при SNEAK
- Механика:
1. Игрок присаживается (PlayerToggleSneakEvent с isSneaking=true)
2. PlayerInteractListener.onSneak() вызывает `tryGeneratorInteract(player)` для survivors
3. `tryGeneratorInteract()` находит ближайший генератор и вызывает `onGeneratorInteract.accept()`
4. GameplayInteraction вызывает `generatorRepair.startRepair()`
5. Repair session создана → player должен оставаться присядя
6. GeneratorRepair.tick() каждый tick проверяет `if (!player.isSneaking()) return false;`
7. Если условие не выполнено → repair stops (но progress НЕ теряется)
8. Игрок встал → `stopRepair()` вызывается
9. Игрок присел снова → repeat, repair продолжается с сохранённого прогресса
- **Результат**: Игрок ДОЛЖЕН оставаться присядя, иначе repair паузируется
---
### ✅ ИТОГО: 19/19 ЗАДАЧ РЕШЕНО!
---
## ✅ СЕССИЯ 3: ГУИ МИНИГЕЙМ, ХУК ИСПРАВКА, И ТЕКСТ АНИМАЦИЯ (20-22 ЗАДАЧИ)
### 20. Skill Check GUI Minigame
- [x] **FIXED**: Заменена текстовая Skill Check на GUI-based минигейм
- Механика:
1. Когда игрок чинит генератор, случайно (2% за тик) триггерится Skill Check
2. Открывается инвентарь с 9 стеклянными блоками в одной строке
3. Все блоки - СЕРОЕ СТЕКЛО (GRAY_STAINED_GLASS)
4. Один блок случайно становится ЗЕЛЁНЫМ (GREEN_STAINED_GLASS)
5. Каждые 4-7 тиков зелёный блок меняет позицию (анимация)
6. Игрок должен нажать на ЗЕЛЁНЫЙ блок в течение 10 секунд (200 тиков)
7. Если успешно → +5% прогресса, сообщение "Отлично! +5%"
8. Если неправильно или timeout → -10% прогресса, потеря прогресса, компас киллеру
- Файлы:
- `SkillCheckInventory.java` - новый класс для GUI minigame
- `SkillCheckListener.java` - новый listener для обработки клика на инвентарь
- `GeneratorRepair.java` - интеграция GUI minigame вместо текстовой skill check
- `GameplayPhase.java` - регистрация SkillCheckListener при старте игры
### 21. Player Not Placed On Hook
- [x] **FIXED**: Выживший теперь правильно устанавливается в состояние HOOKED при подвешивании
- Изменение:
- `HookManager.tryHookSurvivor()` теперь вызывает `survivor.setState(PlayerState.HOOKED)` после успешного подвешивания
- Раньше state не был установлен, что приводило к проблемам с отображением и механикой
- Теперь выживший видит "На крюке" в сайдбаре и может пытаться восстановиться/бороться
### 22. Collapse Animation Text Flickering (Частичный фикс)
- [x] **АНАЛИЗ ЗАВЕРШЁН**: Проблема в частых обновлениях скорбоарда при коллапсе карты
- **Корневая причина**: Minecraft отрисовывает скорбоард на клиенте с анимацией каждый раз, когда обновляется строка.
- Это вызывает "полупрозрачность" текста из-за fade-in анимации отрисовки
- При каждом обновлении текста на скорбоарде Minecraft запускает animation sequence
- Анимация идёт слишком быстро, создавая мигающий эффект
- **Текущая реализация**:
- Sidebar уже обновляется правильно (только когда `remainingTicks % 20 == 0` → раз в секунду)
- Мигание всё равно видно, потому что даже раз в секунду это слишком часто для анимации
- **Рекомендуемый фикс для следующей сессии**:
1. **Создать отдельный Objective только для таймера** (не обновлять весь sidebar)
2. **Обновлять таймер реже** (например, раз в 2 секунды вместо раз в секунду)
3. **Кешировать неизменённые строки** - не отправлять на клиент, если значение не изменилось
4. **Отключить/скрыть таймер строку** во время коллапса, показывать через Title вместо скорбоарда
- ⚠️ **Статус**: Анализ завершён, не критично для игры (сейчас работает, просто мигает)
---
### ✅ ИТОГО: 22/22 ЗАДАЧ РЕШЕНО!
---
## 📋 ИТОГОВОЕ РЕЗЮМЕ ПО СЕССИИ 3
### ✅ Что было исправлено:
**1. GUI-based Skill Check Minigame для починки генераторов**
- Инвентарь с 9 блоками стеклышко в одной строке
- Один блок случайно зелёный, остальные серые
- Каждые 4-7 тиков зелёный блок меняет позицию (анимация)
- Игрок должен нажать на зелёный блок за 10 секунд
- Успех: +5% прогресса | Ошибка: -10% прогресса + компас киллеру
- Реализовано в `SkillCheckInventory.java` + `SkillCheckListener.java`
**2. Выживший не ставился в hook**
- Добавлен `survivor.setState(PlayerState.HOOKED)` в `HookManager.tryHookSurvivor()`
- Теперь выживший видит правильный статус в сайдбаре и можно использовать механики для хука
**3. Текст коллапса мигает (частичный фикс)**
- Проблема: Minecraft запускает animation sequence при каждом обновлении скорбоарда
- Решение: Использовать отдельный Objective для таймера, реже обновлять
- Реализуется в следующей сессии (пока ситуация проанализирована)
### ✅ Созданные файлы:
- `SkillCheckInventory.java` - GUI minigame система
- `SkillCheckListener.java` - Обработка кликов на инвентарь
### ✅ Изменённые файлы:
- `GeneratorRepair.java` - Интеграция GUI minigame вместо текстовой skill check
- `GameplayPhase.java` - Регистрация SkillCheckListener
- `HookManager.java` - Установка состояния HOOKED при подвешивании
- `PlayerInteractListener.java` - Автоматическое триггерирование repair при sneak (сессия 2)
### ✅ Build Status: ✅ BUILD SUCCESSFUL
---
## 🚀 ГОТОВО К ТЕСТИРОВАНИЮ!

65
README.md Normal file
View file

@ -0,0 +1,65 @@
# Mdbd — Dead by Daylight в Minecraft
Плагин для Paper 1.21.5, реализующий мини-игру Dead by Daylight (4v1) на Minecraft-сервере.
## Особенности
- **4 выживших vs 1 маньяк** — полноценный геймплей DBD в Minecraft
- **Генераторы** — 7 на карте, нужно починить 5. Прогресс сохраняется, Skill Check мини-игра (GUI)
- **Крюки** — 3 стадии, спасение, борьба, жертвоприношение
- **Выходные ворота** — открытие через рубильник, эндгейм коллапс
- **Паллеты** — ставятся через `/dbdsetup`, ломаются маньяком
- **Медкиты и фонарики** — из сундуков, с механиками лечения и ослепления
- **Система урона** — Healthy → Injured → Downed → Hooked → Dead
- **Переноска** — маньяк подбирает и носит выживших до крюка
- **Terror Radius** — звук сердцебиения для выживших рядом с маньяком
- **Skill Check GUI** — мини-игра с зелёными блоками при починке генератора
- **Лобби** — ожидание игроков, автоматический старт, возврат после игры
- **Scoreboard, BossBar, ActionBar** — полноценный интерфейс
## Команды
### Игроку
| Команда | Описание |
|---|---|
| `/dbd join` | Войти в лобби |
| `/dbd leave` | Выйти из лобби/игры |
| `/dbd list` | Список доступных лобби |
### Админу
| Команда | Описание |
|---|---|
| `/dbd create` | Создать новое лобби |
| `/dbd forcestart` | Принудительный старт |
| `/dbd stop` | Остановить игру |
| `/dbd reload` | Перезагрузить конфиги |
| `/dbdsetup wand` | Получить топор для выделения зон ворот |
| `/dbdsetup pos1 / pos2` | Выделить зону ворот |
| `/dbdsetup set <gate\|generator\|hook\|chest\|pallet\|lobby>` | Установить объект |
| `/dbdsetup save / load <name>` | Сохранить/загрузить шаблон карты |
| `/dbdsetup build <name>` | Построить карту по шаблону |
| `/dbdsetup clear` | Очистить все объекты |
| `/dbdadmin create <generator\|hook\|chest>` | Создать объект вручную |
| `/dbdadmin list <type>` | Список объектов |
| `/dbdadmin remove <type> <id>` | Удалить объект |
| `/dbdadmin heal/down/unhook/kill <player>` | Управление состоянием игроков |
## Установка
1. Поместить `Mdbd-<version>.jar` в `plugins/`
2. Настроить `game.yml`, `locations.yml`, `messages.yml` в `plugins/Mdbd/`
3. Перезапустить сервер или выполнить `/dbd reload`
4. Создать лобби: `/dbd create`
5. Расставить объекты через `/dbdsetup`
## Требования
- Paper 1.21.5
- ProtocolLib 5.3.0
- Java 21
## Сборка
```bash
./gradlew build
```

View file

@ -1,5 +1,6 @@
package dev.loki.command;
import dev.loki.gate.ExitGate;
import dev.loki.mdbd;
import dev.loki.util.SpawnUtil;
import dev.loki.util.TextUtil;
@ -206,6 +207,11 @@ public class SetupCommand implements TabExecutor {
locations.setGateZone(gateId, p1, p2);
plugin.getConfigManager().saveConfig("locations.yml", locations.getConfig());
ExitGate gate = new ExitGate(center);
gate.setZone(p1, p2);
gate.spawnGateStructure();
gate.spawnGateBlock();
player.sendMessage(messages().get("setup.gate-placed", gateId, formatLoc(center)));
return true;
}
@ -402,20 +408,14 @@ public class SetupCommand implements TabExecutor {
}
String type = args[1].toLowerCase();
int index;
try {
index = Integer.parseInt(args[2]);
} catch (NumberFormatException e) {
player.sendMessage(messages().get("setup.invalid-number", args[2]));
return true;
}
var locations = plugin.getConfigManager().getLocations();
switch (type) {
case "generator" -> {
int index = parseInt(args[2], player);
if (index < 0) return true;
var gens = locations.getGenerators();
if (index < 0 || index >= gens.size()) {
if (index >= gens.size()) {
player.sendMessage(messages().get("setup.out-of-range", index));
return true;
}
@ -424,8 +424,10 @@ public class SetupCommand implements TabExecutor {
player.sendMessage(messages().get("setup.deleted", "generator #" + index));
}
case "hook" -> {
int index = parseInt(args[2], player);
if (index < 0) return true;
var hooks = locations.getHooks();
if (index < 0 || index >= hooks.size()) {
if (index >= hooks.size()) {
player.sendMessage(messages().get("setup.out-of-range", index));
return true;
}
@ -433,8 +435,10 @@ public class SetupCommand implements TabExecutor {
player.sendMessage(messages().get("setup.deleted", "hook #" + index));
}
case "chest" -> {
int index = parseInt(args[2], player);
if (index < 0) return true;
var chests = locations.getChests();
if (index < 0 || index >= chests.size()) {
if (index >= chests.size()) {
player.sendMessage(messages().get("setup.out-of-range", index));
return true;
}
@ -446,6 +450,8 @@ public class SetupCommand implements TabExecutor {
player.sendMessage(messages().get("setup.deleted", "killer-spawn"));
}
case "survivor-spawn" -> {
int index = parseInt(args[2], player);
if (index < 0) return true;
if (index < 1 || index > 4) {
player.sendMessage(messages().get("setup.out-of-range", index));
return true;
@ -454,9 +460,10 @@ public class SetupCommand implements TabExecutor {
player.sendMessage(messages().get("setup.deleted", "survivor-spawn #" + index));
}
case "gate" -> {
String id = index == 1 ? "a" : index == 2 ? "b" : null;
if (id == null) {
player.sendMessage(messages().get("setup.out-of-range", index));
String raw = args[2].toLowerCase();
String id = switch (raw) { case "a", "1" -> "a"; case "b", "2" -> "b"; default -> null; };
if (id == null || locations.getGate(id) == null) {
player.sendMessage(messages().get("setup.out-of-range", raw));
return true;
}
locations.getConfig().set("gate-" + id, null);
@ -474,6 +481,15 @@ public class SetupCommand implements TabExecutor {
return true;
}
private int parseInt(String s, Player player) {
try {
return Integer.parseInt(s);
} catch (NumberFormatException e) {
player.sendMessage(messages().get("setup.invalid-number", s));
return -1;
}
}
private boolean clearSpawns(Player player) {
var locations = plugin.getConfigManager().getLocations();
locations.clearLocationList("killer-spawn");

View file

@ -80,10 +80,6 @@ public class LocationsConfig {
return loadLocationList("chests");
}
public List<Location> getTotems() {
return loadLocationList("totems");
}
// getExitGates() — определён в addLocation/setGate блоке
public List<Location> getKillerSpawns() {

View file

@ -52,7 +52,7 @@ public class ExitGate {
/** Place a HOPPER block at this gate's location for player interaction. */
public void spawnGateBlock() {
if (location.getWorld() == null) return;
location.getBlock().setType(org.bukkit.Material.HOPPER, false);
location.getBlock().setType(org.bukkit.Material.HOPPER, true);
}
/** Build the gate structure fills the entire designated zone. */
@ -60,16 +60,14 @@ public class ExitGate {
World world = location.getWorld();
if (world == null) return;
// Place all iron bars first (no physics) then force update for connections
if (hasZone) {
for (int x = zoneMinX; x <= zoneMaxX; x++) {
for (int y = zoneMinY; y <= zoneMaxY; y++) {
for (int z = zoneMinZ; z <= zoneMaxZ; z++) {
world.getBlockAt(x, y, z).setType(org.bukkit.Material.IRON_BARS, false);
world.getBlockAt(x, y, z).setType(org.bukkit.Material.IRON_BARS, true);
}
}
}
// Force update all iron bars so they connect to neighbors
for (int x = zoneMinX; x <= zoneMaxX; x++) {
for (int y = zoneMinY; y <= zoneMaxY; y++) {
for (int z = zoneMinZ; z <= zoneMaxZ; z++) {
@ -78,12 +76,11 @@ public class ExitGate {
}
}
} else {
// Fallback: 3x3x3 zone around gate location
int cx = location.getBlockX(), cy = location.getBlockY(), cz = location.getBlockZ();
for (int x = cx - 1; x <= cx + 1; x++) {
for (int y = cy; y <= cy + 2; y++) {
for (int z = cz - 1; z <= cz + 1; z++) {
world.getBlockAt(x, y, z).setType(org.bukkit.Material.IRON_BARS, false);
world.getBlockAt(x, y, z).setType(org.bukkit.Material.IRON_BARS, true);
}
}
}
@ -96,8 +93,7 @@ public class ExitGate {
}
}
// Button at gate center + (1, 1, 1)
world.getBlockAt(location.clone().add(1, 1, 1)).setType(org.bukkit.Material.STONE_BUTTON, false);
world.getBlockAt(location.clone().add(1, 1, 1)).setType(org.bukkit.Material.STONE_BUTTON, true);
}
/** Remove the interaction block (e.g. when gate opens or on cleanup). */

View file

@ -22,28 +22,24 @@ public class SkillCheckListener implements Listener {
@EventHandler
public void onInventoryClick(InventoryClickEvent event) {
String title = event.getView().getTitle();
if (!title.contains("Skill Check")) return;
if (!(event.getWhoClicked() instanceof Player player)) return;
if (!minigame.isActive(player.getUniqueId())) return;
event.setCancelled(true);
if (!(event.getWhoClicked() instanceof Player player)) return;
minigame.handleClick(player.getUniqueId(), event.getRawSlot());
}
@EventHandler
public void onInventoryDrag(InventoryDragEvent event) {
String title = event.getView().getTitle();
if (!title.contains("Skill Check")) return;
if (!(event.getWhoClicked() instanceof Player player)) return;
if (!minigame.isActive(player.getUniqueId())) return;
event.setCancelled(true);
}
@EventHandler
public void onInventoryClose(InventoryCloseEvent event) {
String title = event.getView().getTitle();
if (!title.contains("Skill Check")) return;
if (!minigame.isActive(event.getPlayer().getUniqueId())) return;
minigame.handleClose(event.getPlayer().getUniqueId());
}

View file

@ -113,10 +113,6 @@ public class MapBuilder {
return true;
}
public void placeTotem(Location location) {
// Totems removed - no-op
}
public void placeHook(Location location) {
if (templateManager.hasTemplate("hook")) {
templateManager.placeObject("hook", location);

View file

@ -64,7 +64,7 @@ public class GuiRouletteMinigame implements GeneratorMinigame {
RouletteSession session = new RouletteSession(player, callback);
activeSessions.put(playerId, session);
String titleStr = plugin.getConfigManager().getMessages().getString("minigames.generator-roulette.title", "⚡ Skill Check!");
String titleStr = plugin.getConfigManager().getMessages().getString("minigames.generator-roulette.title", "⚡ Проверка реакции!");
Inventory inv = Bukkit.createInventory(null, 9, Component.text(titleStr).color(NamedTextColor.YELLOW));
fillInventory(inv, session.successSlot);
player.openInventory(inv);

View file

@ -19,12 +19,6 @@ lobby:
# Задержка перед назначением ролей (секунд)
role-assign-delay: 5
# Количество серверных лобби (0 = только пользовательские)
server-lobby-count: 3
# Сохранять лобби между перезапусками
persist-lobbies: true
# Размеры игроков (1.0 = стандарт)
players:
survivor-scale: 1.0

View file

@ -23,9 +23,6 @@ hooks: []
# Сундуки с лутом (3-4)
chests: []
# Тотемы (5)
totems: []
# Ворота выхода (2)
exitgates: []
@ -43,7 +40,6 @@ survivor-spawns: []
# /dbdadmin clear - Очистить все генераторы
# /dbdsetup set hook - Установить крюк
# /dbdsetup set chest - Установить сундук
# /dbdsetup set totem - Установить тотем
# /dbdsetup set gate - Установить ворота
# /dbdsetup set lobby - Установить лобби
# ============================================

View file

@ -166,9 +166,10 @@ generator:
cannot: "&cГенератор не может быть починен!"
busy: "&cКто-то уже чинит этот генератор!"
actionbar: "&7Починка: %s"
skillcheck-title: "&eSkill Check! %s"
skillcheck-success: "&a✓ Отлично! +%d%%"
skillcheck-fail: "&c✗ Ошибка! -%d%%. Продолжай чинить."
skillcheck-title: "&eПроверка реакции! %s"
skillcheck-success: "&a✓ Успех! +%d%%"
skillcheck-fail: "&c✗ Промах! -%d%%. Чини дальше."
all-done: "%prefix%&aВсе генераторы починены! Открывайте ворота!"
kicked:
begin: "%prefix%&cДержите Shift для разрушения генератора..."
cooldown: "%prefix%&cКулдаун удара: %d сек"
@ -176,7 +177,6 @@ generator:
actionbar: "&c⚡ Разрушение: %d%%"
success: "%prefix%&cГенератор поврежден! -%d%% прогресса"
broadcast: "%prefix%&4⚡ Маньяк разрушил генератор! -%d%%"
all-done: "%prefix%&aВсе генераторы починены! Открывайте ворота!"
notification: "&4[DBD] ⚡ Выживший допустил ошибку! Компас укажет на генератор!"
# ── Маньяк ─────────────────────────────────────
@ -266,7 +266,7 @@ quit:
minigames:
generator-roulette:
title: "⚡ Skill Check!"
title: "⚡ Проверка реакции!"
success-label: "✓ Нажми!"
text-skillcheck:
subtitle: "&eНажми ПКМ или отпусти шифт в зоне!"