From 89dc9eb00d3f05862600d06ce1690567e232539a Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Thu, 4 Jun 2026 12:11:04 +0200 Subject: [PATCH] feat(gameplay): implement hook sabotage, generator kick, jump listener, and boss bar UI - Add HookSabotage system for survivors to disable hooks temporarily - Add GeneratorKick killer ability to regress generator progress - Add PlayerJumpListener to track survivor jump actions - Add BossBarManager for displaying game state and objectives - Update generator regression logic to support killer kick mechanic - Update KillerController to integrate generator kick ability - Update PlayerInteractListener to handle new interaction types - Refactor game phases to use BossBarManager for UI updates - Update .gitignore to exclude compiled bin/ directory - Simplify PLAN.md documentation for easier maintenance --- .gitignore | 2 + PLAN.md | 463 +++++++----------- bin/main/dev/loki/config/GameConfig.class | Bin 5573 -> 11571 bytes .../core/GameSession$PlayerJoinEvent.class | Bin 1427 -> 1427 bytes .../core/GameSession$PlayerLeaveEvent.class | Bin 1432 -> 1432 bytes bin/main/dev/loki/core/GameSession.class | Bin 7798 -> 7952 bytes .../dev/loki/gate/ExitGate$GateState.class | Bin 1180 -> 1180 bytes bin/main/dev/loki/gate/ExitGate.class | Bin 3276 -> 3813 bytes bin/main/dev/loki/gate/GateManager.class | Bin 4717 -> 4684 bytes .../gate/GateOpening$OpeningProgress.class | Bin 661 -> 661 bytes .../loki/gate/GateOpening$OpeningTask.class | Bin 2162 -> 2265 bytes bin/main/dev/loki/gate/GateOpening.class | Bin 4412 -> 4448 bytes bin/main/dev/loki/hook/Hook.class | Bin 2265 -> 2461 bytes bin/main/dev/loki/hook/HookManager.class | Bin 6333 -> 7315 bytes .../loki/hook/HookRescue$RescueSession.class | Bin 1538 -> 1547 bytes bin/main/dev/loki/hook/HookRescue.class | Bin 5418 -> 6796 bytes bin/main/dev/loki/hook/HookSabotage$1.class | Bin 0 -> 2656 bytes bin/main/dev/loki/hook/HookSabotage$2.class | Bin 0 -> 1434 bytes bin/main/dev/loki/hook/HookSabotage.class | Bin 0 -> 5419 bytes bin/main/dev/loki/hook/HookSacrifice.class | Bin 4049 -> 4503 bytes bin/main/dev/loki/hook/HookStruggle.class | Bin 5280 -> 5641 bytes .../dev/loki/killer/GeneratorKick$1.class | Bin 0 -> 2693 bytes .../dev/loki/killer/GeneratorKick$2.class | Bin 0 -> 838 bytes bin/main/dev/loki/killer/GeneratorKick.class | Bin 0 -> 6005 bytes .../loki/killer/KillerCarry$CarryData.class | Bin 663 -> 663 bytes bin/main/dev/loki/killer/KillerCarry.class | Bin 6731 -> 9633 bytes .../dev/loki/killer/KillerController.class | Bin 4977 -> 3915 bytes .../listener/PlayerInteractListener.class | Bin 8758 -> 9278 bytes .../loki/listener/PlayerJumpListener.class | Bin 0 -> 2436 bytes bin/main/dev/loki/objective/Generator.class | Bin 3846 -> 3986 bytes .../dev/loki/objective/GeneratorManager.class | Bin 5609 -> 5264 bytes .../loki/objective/GeneratorRegression.class | Bin 2679 -> 2300 bytes .../GeneratorRepair$RepairSession.class | Bin 718 -> 718 bytes .../dev/loki/objective/GeneratorRepair.class | Bin 8025 -> 10680 bytes bin/main/dev/loki/phase/EndgamePhase$1.class | Bin 785 -> 785 bytes bin/main/dev/loki/phase/EndgamePhase.class | Bin 8421 -> 10644 bytes .../GameplayPhase$VanillaDamageBlocker.class | Bin 1442 -> 1434 bytes bin/main/dev/loki/phase/GameplayPhase.class | Bin 18754 -> 25340 bytes bin/main/dev/loki/phase/LobbyPhase.class | Bin 5285 -> 5811 bytes .../role/RoleAssigner$RoleAssignedEvent.class | Bin 1610 -> 1610 bytes bin/main/dev/loki/role/RoleAssigner.class | Bin 7007 -> 7546 bytes .../dev/loki/sound/TerrorRadiusSound.class | Bin 3415 -> 3607 bytes .../loki/survivor/SurvivorController.class | Bin 2548 -> 2907 bytes .../dev/loki/survivor/SurvivorHealing.class | Bin 5528 -> 6297 bytes bin/main/dev/loki/ui/BossBarManager$1.class | Bin 0 -> 625 bytes bin/main/dev/loki/ui/BossBarManager.class | Bin 0 -> 7318 bytes bin/main/dev/loki/ui/Sidebar.class | Bin 8280 -> 12263 bytes bin/main/game.yml | 188 +++++-- src/main/java/dev/loki/config/GameConfig.java | 283 +++++++---- src/main/java/dev/loki/core/GameSession.java | 5 +- src/main/java/dev/loki/gate/ExitGate.java | 25 +- src/main/java/dev/loki/gate/GateManager.java | 3 +- src/main/java/dev/loki/gate/GateOpening.java | 14 +- src/main/java/dev/loki/hook/Hook.java | 3 + src/main/java/dev/loki/hook/HookManager.java | 22 +- src/main/java/dev/loki/hook/HookRescue.java | 103 ++-- src/main/java/dev/loki/hook/HookSabotage.java | 150 ++++++ .../java/dev/loki/hook/HookSacrifice.java | 6 + src/main/java/dev/loki/hook/HookStruggle.java | 15 +- .../java/dev/loki/killer/GeneratorKick.java | 175 +++++++ .../java/dev/loki/killer/KillerCarry.java | 270 +++++----- .../dev/loki/killer/KillerController.java | 10 +- .../loki/listener/PlayerInteractListener.java | 13 + .../dev/loki/listener/PlayerJumpListener.java | 55 +++ .../java/dev/loki/objective/Generator.java | 6 + .../dev/loki/objective/GeneratorManager.java | 67 +-- .../loki/objective/GeneratorRegression.java | 22 +- .../dev/loki/objective/GeneratorRepair.java | 36 +- .../java/dev/loki/phase/EndgamePhase.java | 340 +++++++------ .../java/dev/loki/phase/GameplayPhase.java | 362 +++++++++----- src/main/java/dev/loki/phase/LobbyPhase.java | 80 ++- src/main/java/dev/loki/role/RoleAssigner.java | 46 +- .../dev/loki/sound/TerrorRadiusSound.java | 22 +- .../dev/loki/survivor/SurvivorController.java | 59 +-- .../dev/loki/survivor/SurvivorHealing.java | 152 +++--- src/main/java/dev/loki/ui/BossBarManager.java | 140 ++++++ src/main/java/dev/loki/ui/Sidebar.java | 178 +++++-- src/main/resources/game.yml | 188 +++++-- 78 files changed, 2293 insertions(+), 1210 deletions(-) create mode 100644 bin/main/dev/loki/hook/HookSabotage$1.class create mode 100644 bin/main/dev/loki/hook/HookSabotage$2.class create mode 100644 bin/main/dev/loki/hook/HookSabotage.class create mode 100644 bin/main/dev/loki/killer/GeneratorKick$1.class create mode 100644 bin/main/dev/loki/killer/GeneratorKick$2.class create mode 100644 bin/main/dev/loki/killer/GeneratorKick.class create mode 100644 bin/main/dev/loki/listener/PlayerJumpListener.class create mode 100644 bin/main/dev/loki/ui/BossBarManager$1.class create mode 100644 bin/main/dev/loki/ui/BossBarManager.class create mode 100644 src/main/java/dev/loki/hook/HookSabotage.java create mode 100644 src/main/java/dev/loki/killer/GeneratorKick.java create mode 100644 src/main/java/dev/loki/listener/PlayerJumpListener.java create mode 100644 src/main/java/dev/loki/ui/BossBarManager.java diff --git a/.gitignore b/.gitignore index 9e4b940..e9b421b 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,5 @@ hs_err_pid* # Common working directory run +bin/ +bin\ \ No newline at end of file diff --git a/PLAN.md b/PLAN.md index 8e3e231..9632a9f 100644 --- a/PLAN.md +++ b/PLAN.md @@ -4,330 +4,203 @@ - **KISS**: Нет абстракций ради абстракций. Простые классы с одной ответственностью. - **DRY**: Общая логика в утилитах, не копипаста. -- **SOLID**: - - SRP: Один класс = одна задача (StateManager только состояния, DamageHandler только урон) - - OCP: Перки через интерфейс, не if-else везде - - LSP: KillerController и SurvivorController наследуются от PlayerController корректно - - ISP: Маленькие интерфейсы (IRepairable, IHealable, IHookable) - - DIP: Высокоуровневые модули зависят от абстракций, не от имплементаций - - max 150 строк/файл -## Структура папок (max 150 строк/файл) +- **SOLID**: SRP, OCP, LSP, ISP, DIP. Max 150 строк/файл. -``` -src/main/java/com/dbd/plugin/ -├── DBDPlugin.java # Точка входа (max 50 строк) -├── config/ -│ ├── ConfigManager.java # Загрузка всех yaml -│ ├── LocationsConfig.java # Координаты объектов -│ └── GameConfig.java # Балансные цифры -├── core/ -│ ├── GameManager.java # Главный контроллер игр -│ ├── GameSession.java # Одна игра (lobby -> end) -│ ├── GameState.java # Enum: LOBBY, IN_GAME, ENDING -│ └── GameEventBus.java # Внутренние события игры -├── phase/ -│ ├── Phase.java # Interface: enter(), tick(), exit() -│ ├── LobbyPhase.java # Ожидание игроков -│ ├── RoleAssignPhase.java # Назначение ролей, таймер 5сек -│ ├── GameplayPhase.java # Основная игра -│ └── EndgamePhase.java # Эндгейм коллапс + результаты -├── player/ -│ ├── BasePlayer.java # UUID, Player объект -│ ├── Survivor.java # Данные выжившего -│ ├── Killer.java # Данные киллера -│ ├── PlayerState.java # Enum: HEALTHY, INJURED, DOWNED... -│ └── PlayerManager.java # Map -├── state/ -│ ├── StateMachine.java # Машина состояний игрока -│ ├── StateTransition.java # Проверка можно ли перейти -│ └── StateHandler.java # Обработчик изменений -├── role/ -│ ├── RoleAssigner.java # Логика выбора кто киллер -│ ├── RoleQueue.java # Очередь на киллера -│ └── SpawnPosition.java # Спавн-точки из конфига -├── survivor/ -│ ├── SurvivorController.java # Действия выжившего -│ ├── SurvivorMovement.java # Sprint, Exhaustion -│ ├── SurvivorHealing.java # Лечение себя/других -│ └── SurvivorInventory.java # Предметы в руках -├── killer/ -│ ├── KillerController.java # Действия киллера -│ ├── KillerAttack.java # ЛКМ, кулдаун, hit detection -│ ├── KillerCarry.java # Подъём, перенос, wiggle -│ └── KillerVision.java # Terror radius, red stain -├── damage/ -│ ├── DamageHandler.java # Нанесение урона (Healthy->Injured...) -│ ├── AttackListener.java # Слушатель ЛКМ киллера -│ └── HitValidator.java # Line of sight, range check -├── objective/ -│ ├── Generator.java # Один генератор -│ ├── GeneratorManager.java # Все 7 генераторов -│ ├── GeneratorRepair.java # Логика починки -│ ├── GeneratorRegression.java # Откат прогресса -│ ├── SkillCheck.java # QTE система -│ └── SkillCheckUI.java # Title-анимация -├── hook/ -│ ├── Hook.java # Один крюк -│ ├── HookManager.java # Все крюки -│ ├── HookStage.java # Stage 1, Stage 2 -│ ├── HookStruggle.java # Skill checks на Stage 2 -│ ├── HookRescue.java # Спасание с крюка -│ └── HookSacrifice.java # Смерть после Stage 2 -├── pallet/ -│ ├── Pallet.java # Один паллет -│ ├── PalletManager.java # Все паллеты -│ ├── PalletDrop.java # Выживший бросает -│ ├── PalletStun.java # Стан киллера -│ └── PalletBreak.java # Киллер ломает -├── window/ -│ ├── Window.java # Одно окно -│ ├── WindowManager.java # Все окна -│ ├── WindowVault.java # Прыжок через окно -│ └── WindowFastVault.java # Быстрый/медленный волт -├── gate/ -│ ├── ExitGate.java # Одни ворота -│ ├── GateManager.java # 2 ворота -│ └── GateOpening.java # 20 сек открытие -├── hatch/ -│ ├── Hatch.java # Люк -│ ├── HatchCondition.java # Условие появления -│ └── HatchEscape.java # Прыжок в люк -├── item/ -│ ├── ItemType.java # Enum: MEDKIT, FLASHLIGHT... -│ ├── BaseItem.java # Interface предмета -│ ├── ItemMedkit.java # Аптечка -│ ├── ItemFlashlight.java # Фонарик -│ ├── ItemToolbox.java # Ящик инструментов -│ ├── ItemKey.java # Ключ от люка -│ ├── ItemMap.java # Карта -│ └── ChestLoot.java # Лут из сундуков -├── chest/ -│ ├── Chest.java # Сундук -│ ├── ChestManager.java # Все сундуки -│ └── ChestOpening.java # 3 сек открытие -├── totem/ -│ ├── Totem.java # Тотем -│ ├── TotemManager.java # 5 тотемов -│ ├── DullTotem.java # Обычный тотем -│ └── HexTotem.java # Проклятый (с перком) -├── perk/ -│ ├── IPerk.java # Интерфейс перка -│ ├── PerkType.java # Enum: SURVIVOR, KILLER -│ ├── PerkRegistry.java # Map всех перков -│ ├── survivor/ -│ │ ├── SelfCarePerk.java -│ │ ├── DeadHardPerk.java -│ │ ├── SprintBurstPerk.java -│ │ └── BorrowedTimePerk.java -│ └── killer/ -│ ├── HexRuinPerk.java -│ ├── CorruptInterventionPerk.java -│ └── BBQChiliPerk.java -├── ui/ -│ ├── ActionBar.java # Нижняя полоска -│ ├── Sidebar.java # Scoreboard -│ ├── TitleUI.java # Title/Subtitle -│ ├── ProgressBar.java # [████░░░░░░] 40% -│ └── StatusIcons.java # ❤ 🩸 💀 -├── sound/ -│ ├── SoundManager.java # Воспроизведение звуков -│ ├── TerrorRadiusSound.java # Сердцебиение -│ ├── GeneratorSound.java # Звуки генератора -│ └── SoundLevels.java # 3 уровня громкости -├── util/ -│ ├── LocationUtil.java # Dist, line of sight -│ ├── PlayerUtil.java # Is sneaking, sprinting -│ ├── TimeUtil.java # Форматирование секунд -│ ├── MathUtil.java # Clamp, lerp -│ └── ParticleUtil.java # Спавн частиц -├── command/ -│ ├── DBDCommand.java # /dbd главная -│ ├── JoinSubcommand.java # /dbd join -│ ├── LeaveSubcommand.java # /dbd leave -│ ├── SetupSubcommand.java # /dbd setgen, sethook... -│ ├── AdminSubcommand.java # /dbd forcestart, stop -│ └── StatsSubcommand.java # /dbd stats -├── listener/ -│ ├── PlayerQuitListener.java # Дисконнект во время игры -│ ├── PlayerInteractListener.java # ПКМ, ЛКМ -│ ├── PlayerMoveListener.java # Движение, ползание -│ └── DamageListener.java # Отмена ванильного урона -├── data/ -│ ├── DataStorage.java # Interface: save(), load() -│ ├── YamlStorage.java # YAML реализация -│ ├── SQLiteStorage.java # SQLite реализация (позже) -│ ├── PlayerStats.java # Статистика игрока -│ └── MatchResult.java # Результат матча -└── exception/ - ├── DBDException.java # Базовое исключение - ├── InvalidLocationException.java - └── InvalidStateException.java -``` +--- -## TODO (по порядку) +## Статус реализации -### Этап 0: Скелет проекта -- [x] Создать `build.gradle` (Paper 1.20.6, Java 21, ProtocolLib) +### ✅ Этап 0: Скелет проекта +- [x] `build.gradle` (Paper 1.21.1, Java 21) - [x] `plugin.yml` с пермишнами - [x] `DBDPlugin.java` — onEnable/onDisable - [x] `ConfigManager` — загрузка yaml без перезаписи -- [x] `LocationsConfig` — структура для всех координат -- [x] `GameConfig` — балансные цифры +- [x] `LocationsConfig` — координаты объектов +- [x] `GameConfig` — все балансные цифры (BossBar, carry, heal limits, lobby timers, sizes, kick cooldown, sabotage) -### Этап 1: Core Game Loop +### ✅ Этап 1: Core Game Loop - [x] `GameState` enum -- [x] `GameSession` — хранит игроков, фазу, таймер +- [x] `GameSession` — хранит игроков, фазу, таймер; передаёт `GateManager` и `collapseSeconds` в `EndgamePhase` - [x] `Phase` interface -- [x] `LobbyPhase` — ожидание, минимум 5 игроков, таймер 60сек -- [x] `RoleAssigner` + `RoleQueue` — кто киллер -- [x] `RoleAssignPhase` — телепорты, задержка 5сек -- [x] `GameplayPhase` — заглушка -- [x] `/dbd join`, `/dbd leave`, `/dbd forcestart` +- [x] `LobbyPhase` — таймер **45 сек при 4 игроках**, **15 сек при 5**, min-players=4, анонс на ключевых секундах +- [x] `RoleAssigner` + `RoleQueue` — выбор киллера; применяет `GENERIC_SCALE` через Attribute API +- [x] `RoleAssignPhase` — телепорты, задержка 5 сек +- [x] `GameplayPhase` — полная логика (см. ниже) +- [x] `EndgamePhase` — `collapseSeconds` из конфига, таймер в `Sidebar`, `GateManager` из сессии, Adventure API title +- [x] `/dbd join`, `/dbd leave`, `/dbd forcestart`, `/dbd stop`, `/dbd reload`, `/dbd lobby`, `/dbd setlobby` -### Этап 2: Игроки и состояния +### ✅ Этап 2: Игроки и состояния - [x] `BasePlayer`, `Survivor`, `Killer` -- [x] `PlayerState` enum: HEALTHY, INJURED, DOWNED, HOOKED, DEAD, ESCAPED +- [x] `PlayerState`: HEALTHY, INJURED, DOWNED, HOOKED, CARRIED, DEAD, ESCAPED - [x] `StateMachine` — переходы с валидацией -- [x] `PlayerManager` — map UUID -> BasePlayer +- [x] `PlayerManager` — map UUID → BasePlayer - [x] `SpawnPosition` — телепорт из конфига +- [x] Перевод в `GameMode.SPECTATOR` при `PlayerState.DEAD` (в `GameplayPhase.checkWinConditions` и `HookSacrifice`) +- [x] Размеры через `Attribute.GENERIC_SCALE`: Survivor=1.0, Killer=1.5 (в `RoleAssigner` и `GameplayPhase`) -### Этап 3: Киллер (базовый) -- [x] `KillerController` — основной класс -- [x] `KillerAttack` — ЛКМ, кулдаун 2.5с, hitbox +### ✅ Этап 3: Киллер +- [x] `KillerController` — без дублирующей регистрации `AttackListener` +- [x] `KillerAttack` — ЛКМ, кулдаун 2.5 с, hitbox - [x] `HitValidator` — line of sight, расстояние -- [x] `DamageHandler` — Healthy->Injured, Injured->Downed -- [x] `KillerCarry` — подъём, перенос, wiggle progress -- [x] `KillerMovement` — скорость 115%, замедление после удара +- [x] `DamageHandler` — Healthy→Injured, Injured→Downed +- [x] `KillerCarry` — побег через **спам пробела** (PlayerJumpListener), шкала в ActionBar, Speed I при побеге +- [x] `KillerMovement` — скорость из конфига, замедление после удара +- [x] `GeneratorKick` — Shift 3 сек → −20% прогресса, кулдаун 30 сек, progress bar +- [x] `BossBarManager` — BossBar с градиентом по дистанции до маньяка, читает все параметры из конфига -### Этап 4: Выживший (базовый) -- [x] `SurvivorController` -- [x] `SurvivorMovement` — sprint, exhaustion 3сек/5сек кулдаун -- [x] `SurvivorHealing` — аптечка 10сек, другие 16сек, прогресс-бар +### ✅ Этап 4: Выживший +- [x] `SurvivorController` — передаёт `GameConfig` в `SurvivorHealing` +- [x] `SurvivorMovement` — sprint, exhaustion +- [x] `SurvivorHealing` — таймеры из конфига (other=15 сек, self=30 сек), лимит **4 лечений** из конфига, sneak-check, range-check - [x] Downed movement — crawl 30% скорости -### Этап 5: Генераторы -- [x] `Generator` класс — прогресс, статус -- [x] `GeneratorManager` — 7 штук, 5 нужно -- [x] `GeneratorRepair` — зажать ПКМ, прогресс в ActionBar -- [x] `SkillCheck` — логика QTE, успех/провал -- [x] `SkillCheckUI` — Title-анимация, круг (текстом или картинкой) -- [x] `GeneratorRegression` — откат 0.25%/сек, Kick 2.5%/сек 30сек +### ✅ Этап 5: Генераторы +- [x] `Generator` — `setUnderRegression(bool, ticks)`, `wasKicked()` +- [x] `GeneratorManager` — `getCompletedCount()` считает живое состояние +- [x] `GeneratorRepair` — ПКМ, прогресс в ActionBar; при фейле skillcheck: `setUnderRegression` + **компас маньяку** на `compass-duration-seconds` +- [x] `GeneratorRegression` — скорость полностью из конфига (`regression-rate`, `kick-regression-rate`), без хардкодированных констант +- [x] `SkillCheck` + `SkillCheckUI` — QTE система -### Этап 6: Крюки -- [x] `Hook` класс -- [x] `HookManager` — все крюки, подвал -- [x] `HookStage` — Stage 1 (60с), Stage 2 (60с) -- [x] `HookStruggle` — skill check каждые 2с -- [x] `HookRescue` — снятие 2сек -- [x] `HookSacrifice` — смерть -- [x] Счётчик крюков — 3 раза макс +### ✅ Этап 6: Крюки +- [x] `Hook` — поле `disabled` + `isDisabled()`/`setDisabled()` +- [x] `HookManager` — `maxHooks` из конфига, `tryHookSurvivor` проверяет лимит → при превышении сразу `DEAD`; `findNearestUnoccupiedHook` и `findAvailableHook` учитывают `isDisabled()` +- [x] `HookStage` — Stage 1 (60 с), Stage 2 (60 с) +- [x] `HookStruggle` — `struggleIntervalTicks` из конфига (`hooks.struggle-interval`) +- [x] `HookRescue` — 60 тиков (3 сек из конфига), sneak-check, range-check, Speed I после спасения +- [x] `HookSacrifice` — смерть + **SPECTATOR mode** +- [x] `HookSabotage` — выживший держит Shift **10 сек** → крюк отключён, восстановление **60 сек** (всё из конфига), progress bar, broadcast -### Этап 7: UI +### ✅ Этап 7: UI - [x] `ActionBar` — статус, прогресс-бары -- [x] `ProgressBar` — [████░░░░░░] формат -- [x] `Sidebar` — генераторы, живые -- [x] `TitleUI` — начало игры, skill check, победа/поражение -- [x] `StatusIcons` — ❤ 🩸 💀 +- [x] `Sidebar` — сердца ♥ (по `hooksTaken`), цвета ников по состоянию, `GeneratorManager` (живой счёт), таймер коллапса +- [x] `TitleUI`, `ProgressBar`, `StatusIcons` +- [x] `BossBarManager` — BossBar для каждого выжившего, градиент по дистанции +- [x] Шкала wiggle в `KillerCarry.showWiggleProgress()` (ActionBar) -### Этап 8: Паллеты и окна -- [x] `Pallet` — standing/dropped/broken -- [x] `PalletDrop` — ПКМ выжившим -- [x] `PalletStun` — 2.5сек стан киллеру -- [x] `PalletBreak` — зажать ПКМ 3сек -- [x] `Window` — класс -- [x] `WindowVault` — 1сек выживший, 1.5сек киллер +### ✅ Этап 8: Паллеты и окна +- [x] `Pallet`, `PalletManager`, `PalletDrop`, `PalletBreak`, `PalletStun` +- [x] `Window`, `WindowManager`, `WindowVault`, `WindowFastVault` -### Этап 9: Эндгейм -- [x] `ExitGate` — открытие 20сек -- [x] `GateManager` — 2 ворота -- [x] `Hatch` — условие появления -- [x] `HatchCondition` — формула: живые < починенные гены -- [x] `HatchEscape` — прыжок = побег -- [x] `EndgamePhase` — коллапс 120сек +### ✅ Этап 9: Ворота и люк +- [x] `ExitGate` — `savedProgress` + `getSavedProgress()`/`saveProgress()`/`restoreProgress()`/`setProgressDirect()`; `reset()` чистит `savedProgress` +- [x] `GateManager` — `activateEndgame()` только включает питание, **без авто-открытия** +- [x] `GateOpening` — **линейный прогресс** (`setProgressDirect`), сохраняет/восстанавливает прогресс при уходе/возврате игрока +- [x] `Hatch`, `HatchCondition`, `HatchEscape` -### Этап 10: Предметы -- [x] `ItemType` enum -- [x] `BaseItem` interface -- [x] `ItemMedkit`, `ItemFlashlight`, `ItemToolbox`, `ItemKey`, `ItemMap` -- [x] `Chest` — 3сек открытие -- [x] `ChestLoot` — весовая таблица +### ✅ Этап 10: Звук +- [x] `TerrorRadiusSound` — `terrorRadius` параметр конструктора (из конфига), дефолт 32 +- [x] `SoundManager`, `GeneratorSound`, `SoundLevels` -### Этап 11: Звуки и Terror Radius -- [x] `SoundManager` -- [x] `TerrorRadiusSound` — 3 уровня по расстоянию -- [x] `KillerVision` — red stain частицы -- [x] Звуки генераторов, взрывы +### ✅ Этап 11: Слушатели +- [x] `PlayerJumpListener` — прыжки для wiggle при переноске +- [x] `PlayerInteractListener` — `setOnKillerSneak` + `PlayerToggleSneakEvent` (маньяк → GeneratorKick, выживший → HookSabotage) +- [x] `PlayerMoveListener`, `PlayerQuitListener`, `DamageListener` -### Этап 12: Data (YAML -> SQLite) -- [x] `DataStorage` interface -- [x] `YamlStorage` — реализация -- [x] `SQLiteStorage` — заглушка -- [x] `PlayerStats` — сохранение BP, матчей -- [x] `MatchResult` — результат матча +### ✅ Этап 12: Данные и хранение +- [x] `DataStorage`, `YamlStorage`, `SQLiteStorage`, `PlayerStats`, `MatchResult` -### Этап 13: Перки (отложено до стабильности) -- [x] `IPerk` interface: onEquip, onTrigger, onRemove -- [x] `PerkRegistry` — фабрика перков -- [x] 4 перка выжившего (Self-Care, Dead Hard, Sprint Burst, Borrowed Time) -- [x] 3 перка киллера (Hex: Ruin, Corrupt Intervention, BBQ) -- [x] `Totem` + `TotemManager` — только с Hex +--- -### Этап 14: Полировка -- [x] Обработка дисконнекта -- [x] Баланс скоростей -- [x] Утилиты (LocationUtil, PlayerUtil, TimeUtil, MathUtil, ParticleUtil) -- [x] Оптимизация (проверка не каждый тик) +## ❌ Не реализовано (следующие этапы) -## Правила <150 строк на файл +### Пользовательские лобби +- [ ] `LobbySession` — пользовательское лобби (пароль, владелец, приватность) +- [ ] `LobbyRegistry` — реестр серверных и пользовательских лобби, YAML persistence +- [ ] `/dbd create [password] [private]` — для обычных игроков (сейчас требует `mdbd.admin`) +- [ ] `/dbd join ` — вход в конкретное лобби -```java -// ✅ Правильно: Controller делегирует -public class KillerController { - private final KillerAttack attack; - private final KillerCarry carry; - private final KillerMovement movement; - - public void onLeftClick(Player target) { - attack.tryAttack(target); - } -} +### Механики крюков +- [ ] `HookRecovery` — самостоятельное слезание с крюка на Stage 2 -// ❌ Неправильно: всё в одном классе -public class KillerController { - // 500 строк логики атаки, переноса, движения... -} -``` - -## Ключевые интерфейсы - -### IPerk -```java -public interface IPerk { - String getId(); - String getName(); - PerkType getType(); // SURVIVOR or KILLER - - void onEquip(BasePlayer player); - void onTrigger(BasePlayer player, PerkTrigger trigger); - void onRemove(BasePlayer player); -} -``` - -### Phase -```java -public interface Phase { - void enter(GameSession session); - void tick(GameSession session); // каждую секунду - void exit(GameSession session); - boolean isComplete(); -} -``` - -### DataStorage -```java -public interface DataStorage { - void saveStats(UUID player, PlayerStats stats); - PlayerStats loadStats(UUID player); - void saveMatch(MatchResult result); -} +### Перки +- [ ] `IPerk` интерфейс, `PerkRegistry` +- [ ] Survivor: `SelfCarePerk`, `DeadHardPerk`, `SprintBurstPerk`, `BorrowedTimePerk` +- [ ] Killer: `HexRuinPerk`, `CorruptInterventionPerk`, `BBQChiliPerk` + +### Предметы из конфига +- [ ] `ChestLoot` — читать веса и типы предметов из `game.yml` (сейчас захардкожено) + +--- + +## Конфиги (game.yml) — актуальные ключи + +```yaml +lobby: + min-players: 4 + max-players: 5 + countdown-4-players: 45 + countdown-5-players: 15 + role-assign-delay: 5 + +players: + survivor-scale: 1.0 + killer-scale: 1.5 + +generators: + total-count: 7 + required-count: 5 + repair-seconds: 90 + regression-rate: 0.25 + kick-regression-rate: 2.5 + kick-regression-percent: 20.0 + kick-duration-seconds: 30 + kick-cooldown-seconds: 30 + kick-charge-seconds: 3 + interaction-range: 3.0 + +hooks: + stage1-seconds: 60 + stage2-seconds: 60 + struggle-interval: 2 + max-hooks: 3 + interaction-range: 3.0 + rescue-seconds: 3 + sabotage-seconds: 10 + sabotage-recovery-seconds: 60 + +healing: + other-heal-seconds: 15 + self-heal-seconds: 30 + medkit-bonus: 0.5 + interrupt-on-move: true + max-heals-per-match: 4 + heal-range: 3.0 + +carry: + escape-jumps: 15 + escape-speed-seconds: 3 + jump-fill-percent: 6.67 + +killer: + attack-cooldown: 2.5 + attack-range: 3.0 + pallet-stun-seconds: 2.0 + pallet-break-seconds: 3.0 + terror-radius: 32 + pallet-interaction-range: 3.0 + +bossbar: + enabled: true + title: "☠ МАНЬЯК РЯДОМ ☠" + color: RED + style: SOLID + appear-range: 32 + full-range: 8 + +endgame: + gate-open-seconds: 20 + collapse-seconds: 120 + chest-open-seconds: 3 + gate-interaction-range: 3.0 + +killer-notification: + compass-duration-seconds: 5 + +scoreboard: + show-hearts: true + heart-symbol: "♥" + color-healthy: "WHITE" + color-injured: "RED" + color-healing: "GREEN" + color-hooked: "DARK_RED" ``` diff --git a/bin/main/dev/loki/config/GameConfig.class b/bin/main/dev/loki/config/GameConfig.class index f15ca2661cafb9ef3d04fc0203c67162363babe4..e057aaf65b57775290056968f3f0a9bd268dd78e 100644 GIT binary patch literal 11571 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 literal 5573 zcmb7HYjhk{9sk`vHq&&|ZkFwX7TQ8fo21>{&?lahHfdr@AbpVZ!J`VZyEnVjoy=@! zW|Oq2=%I>=LOm!bR8c{zf|g22D*__uQIX^8)E9aVe$}t?!A~6jcV_O)>~{7;lau7` z|M9#3_y6YY4_vhsoZq8P@Im+3hB*wWW9p=u*HxpO z-(NV!iw;A=lVrGg#I(x!LcLnm9ACX|sg7nE`EgC>`Q7x(gYwCJq`X}-G-n6H=B}&O z?mo;gXV5J1cFe~7B-)Tb~>e!=oFm!Hg|^jxLVg8n_=OIX7GLW zi2}C{ss)`hq({u6svlM@O?>u=bDWAs($|f&j!U0{B@CUqSty*yO=w28rmH8oWw+y6 zEN{ay;)OvQHXL5&Rub3YdWN=1Rj>2?;|zHnVH0W`)=0ss0+Y%ND1r zO(RQp@x;zQP@&RSJ2qiU8#Xso3Xrki`Cpo)8JWKJ4xogwaNWbu#4P|h*%Uc(G^*{ z#(62xugD8`Gc1kB+4W+P{LdClqi!eqGg9n!%1%UVT-Ee!jT@@&oJjPqky87pYKx{W zQdF}{*T-x@wI~%6HJv+L!2t$k?dsm%-Mf2x6^t^hh?CchP~)JhA>@SywO_18K`mCx z7FAVA!CkI~48%QDi~b#zTDHQ;EekSS27 zN2pvSTFKSBWtV3qHCLu$npHbzJ8GHV9A>W4$k`Zjj!}fAj#V$0sRuM_T#MROflkB2 zV@^;*xhDk%5hu(^KEVwqC#r#43Tjk|O~h&@M2q39?VYi-O+p9eD< zESXWtzU#%1@Zyyueu`Ha<`Jy7rQ#hK>K>xllh7TsvYNpb`t%`xH_@OZ3dND8U0_8( z+8B`LlN1H!HZdG&jJhadaE*Cu>F677Stt7M_R5=TI}jNZQ_Pn;BH1D~WBw>RLX*tp zlRpvim^;LL#e#Q%=a^I+6obqc*ddWcCI;L|G3XD9ME_>V6oz}uQ86jJWFBxw#28J8 zgA_4xPuT7ecZsp!Q$^?wy2D`9odsSdyhkd-rmpU*ew5NXZOp7&MJ`^uv=qYkb51<( za09*QW|Qe;rg)&zX*r$SpwRDm^l2Yv(KoQ5=OWsB*1wF7o*67WPn21t0s0Z|6NP@y zqwjWfU_P!P%3@Co87!htr0=3LqSE8n!=CjnX)(I`{hst<*#^AIjwOta| z2E?vZng(_T!j8eYL$ff;z9}X zSO#B`=f@-GucR_k_{r3?Jbo=O^yxVsEv%q7(M$EQkZLDI)zXQTR8!qlHJho1c3=sH zu@r~se-{V+Br;)M@%RVu742IZQ z>|y6|8+#4I>^B%;m$8?(6N37 zUw;Zo_qbsO&x%0Ic3WwID|Cn&=rDoY<$|vA6oWQegzw_4xQGJuJ$#?q=?AVpdByNN z#lh>^Euuj3Z zImV1)1S1)k@lt@%=*ikRGs+Q&(31-RM57rrKw`|OMleD%UJfuC&FG0U!;C_g5RXJ4!sNag#I(;( zOsAWei45MNm}c-xvpG4BMSwbk&ylEQ}xm2A@^(qDIG-na$r(pKZ#{X zNX;ob8vzOX>CY|95nHsCInPIsLUaDo0!i%PR!HY!k^UMWH4Yov;4wA*QUoJx&%fdC e{;QZr+;G&Sz7qjq_Cs za9Qf}rb3l2qs#eZ>NBQdDlyYlyxoznl+t2;*)id~G8K7N5G_@)3wbGKD(B~%WmLt- zoFU^Xf}c+~{rmFMwkehom|&2qY~bFE6(Ros2J1qvAioc#eYl|yLvk1<>c^Ws)FUtj zx5>H)DqCEEl2vHHjcCLzsK>3i6C30$BSweYafh5z;KV_=CDv!Iep7L5zTeK-`}j}H~p%CT?& zV|C$veA!k;|k zaX0)$*gebf!wGQfH2ebojmxi*T@$EfZRao=o`Sg-Q$4+8oaw+=U3N%1cJgAcpV#=( zxz+1*aRPVJ|J-EZC%lF9BoBMj6)V~Jv8*&c<8^qRo)fK7vW^J)9Ng{8;3K{$dHJTy z2Q8VJGRmYZ?S0Mg>^!_MC!L?NDSHJxR7opowd~he1)%+`#xVIGn(;Jo delta 1437 zcmZ8gTWnNS6kU7n^bT|Hote&zbvo}(J00xIP(>xu^G{HyWhYd(tg7|1m zCW?t4`a#?#`a!VKFA#mY;o_(1VpD~c#0iXsZ4``ml&O#Hd$>~r^8YwvZ= z-n{5cPvgN0uZ#m&Ld$&c5aPVPA- z(2raALH^$pSf~H2@Td5Apy)kq;Wv&2yY#d8%ZESt=3vr`b2!ff!NRPwxM1NQ3AP50 zlEIro_fnY8h1MB<5H(v!Bg1`-%e@(kQas%Fj6U01_IPBrb~(-C zVq_}P*@(O$z$+~bAs_P5d`?BXy^9F#T#cm74fz}T``2z9@X_L34@RHW{M+D#N9RXNjei+l_4^OLb$@p{~WTP42O$;WNDU7`>ocooecJS|ZJv|Lk| zPsXn8xf4G0A&fNwSc?|iRR_+wU?7Lr5Ol#BC@kC8v4;nmW0f~bqd1^pRdE>QF--QB zM)2-ScyEWuczH&8U8E|<0By@e2gRV%?aWr!6{@wnCF(tj!KQz zu=~5a0t*{$&K4(ohBJUq@TqVK13nXWkL*9k7cTpt7^>J?ZT3-o>5&#EZY=Lb#OY+Z zw>*NQZYPe#5y1fWi4*sWor6;41L#&>JJPK)(ygv^5EtQFTUxn33v?biA10)P?{JLA zDlg(;5 zc@FDx34XyU`Q`-NC95Ja6u~HdC2Y4P)bnx*d!&xSa0?^+jx)R^ky`jibtnF=y-4SV z9u>PDtAj;cm~upFmRBXB#HSN^+RQ66P2}OuWI6ss9mM=ki8S)^hGd?eQkhx<>0Fg) zi;DPEqRB&ko=6rtOj2E{mKKOlYHsvm=|%F_UXRnx5U)(d8Ujdw_*n8GncOw0hkY4k zgX{;>rR&lPQh)o;$*gRXS$SSSFW5CYgO+q_48u=ZY7q*zLE)wxpGX&|Y8Tx~ZCuLa zY1oeP)XsgGY^9)~rsPb_%QBgx^3KNURHCj55XE6C)>PzaQb|hnP?_e)D*aOzG`fPW lqHbCsCkbg|Qcf2lj%itY5T~o@8d@UjQbQ7SF=`Ui{{nxZ15^M2 diff --git a/bin/main/dev/loki/gate/ExitGate$GateState.class b/bin/main/dev/loki/gate/ExitGate$GateState.class index c9a1e1f25e20f698a80b53b6e9e0b44250e7aaaa..65b590aefef0a83a95b8bfac24446428f06fded4 100644 GIT binary patch delta 31 ncmbQkIfrw@B4%EG23`gM1}O$X26YC$$?KU-82KhYVcr1%XOjl= delta 31 ncmbQkIfrw@B4%Db23`h!1}O#s26YDB$?KU-7 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 N#AM_xCi$O2e*p2kk3;|f delta 691 zcmZ9K$!k+l6vn@s;k}pVgXBp_@sRa=L2h$c?0x{@OP3DS)V5&Q?Lp2W}wH}~Fi?)jbXyNA0N{ux$3|1P}) zuoIv1>ZoL>FfpKE5MBYz>QDCdCvvGwI&*}^rKZq%4MWHZxYAZNU9P{V;S$c$FKMfE zRl_x0r)JrdhBb`fCLNZet})z}aZA8Y+hxB~!yVkEyu4c(!vhTuF;2_!9u6L9c#J0$ zQ|cT~H9VtUB`(dPAY+ael!*N$UeOQbo0y?RSH$;*{q4*jX1kB&WPF%uQN7ONL$JZlk8Eyq%puq~i$D<_0&NrUMT_VZn1>lN9R-{;3pmB8 z9)XcR-}8*vv5wauBwpL#L>wU`umSr~&*vr_ziN{{6^kGaB*!M0{b}9L0@#Y zb1jFb>8pE_aj=4D9D^JFLj=z71l^u!>$zbln_5*YrIAIZ>pSmj|F^$@fYdEkuLxM diff --git a/bin/main/dev/loki/gate/GateOpening$OpeningProgress.class b/bin/main/dev/loki/gate/GateOpening$OpeningProgress.class index cfa3d52337559068b1ada09c7405c2b6c613b526..c261464f4a8013148f4ad10f67ff6a5f124e4bf3 100644 GIT binary patch delta 25 gcmbQrI+b;U0~2o=11Cc|10O>MgD^wpWM3u^07yy&CIA2c delta 25 gcmbQrI+b;U0~2pD11CcY10O>wgD^wdWM3u^07u3J8UO$Q diff --git a/bin/main/dev/loki/gate/GateOpening$OpeningTask.class b/bin/main/dev/loki/gate/GateOpening$OpeningTask.class index e62ee194011da8109446158d1690e5173a1d2aff..31cfe4474f8c14175f1076112368c7c3d44fecfa 100644 GIT binary patch delta 845 zcmZ9JOHWf#6otRj2L-%P1Y4~d4aNrA5)l#KZ?Ou}pnxF0YrWLk+E!ZenN&;^jhfip zC{ae5_yY*_CJUi0nG%xB;rw_Mm1 zeq+>GKiLwqDQ~gpU9Yi;T7{O2j_4hwrOm) zjzqH>?Hn=U z_7=xcML@g*?h3+qm*J;V<2YT$cYAAjh!Ywo=`li%;JPq<8mH)2IP-~EGN(`-)_|uq z203Ho9FamLA=YpapuIGDYmn%!lDC~`ac5Bj08d1xqB9qnAeU_J17}L0du2`Pa z^Gh}DiD5mKS2!Bm1N~OBxW-iy3U@4XJ)6?=dbe04(s4ywW7m|=nN(F-D!4&4!_<*8 zie-BX?1rxmFJw;{&KVk5dSA63&bByzo zH>{F2K^zi)Nz2uO<@^x0BuaWrkm5SFZYkYBnv6`B6^Vzkc|0!s-g*2k!j8Z^XYE4X a5Yi^^mJ_U%Q&=86FQ^1>3XUxBoBsmvO}nB1 delta 714 zcmZvZOHWf#6otRjd)rdHRHU|AO^6s8q$Oa%x1e6c7f>mRC_bsBBCocAf*K!0G{Jz6 zkUKL>O!Nn!wFwT04rJiK=+J-Q*fBBg3p#Uf&e~_Keb(M*pC223m6m?_{b30($+~eU zLe_V!Vj-GzsGvkar$H@s)=x)$FltawnZlJ!q?4&(4-s}5>}HSEpjEl{8SLkP)vHx( zZRC)fCPm3$Vj@vDo*NjhizTuKhdE-+Yf;r~ebs79Oaki&am?U2CoGTN;Atb~rd<)3 zn+r(Y({xxZdbr%A)8Gte70%p1a%fDUP8Cb>a|Yd;X1kXjgNt^OCzDO3yX?eb z+PG|Rg{unhc(yN*OT|Ys{iCTd@z8g)RCn7py#@)czcZavs$>KYX}o0ck+r_LnkMg{ z7_7wp; zl4W=4FK}(OtUN_ylkDC2WzSK>xI`0d;}(_NmLIuGBll!<_vv8r0dXFZWJ>1ph*2Ih z#;+)FZj$Ho5eOPQ!}N1gni-G` rKdu%0ZbIu<2)Qx!?JIO@B32EG>R^ar!7_tm=_JTke+N diff --git a/bin/main/dev/loki/gate/GateOpening.class b/bin/main/dev/loki/gate/GateOpening.class index e11f5616c07d214c6ba5774eeb83ff5d22ba9f5d..008d3a0be0db9109edf4854efa882105b7d92250 100644 GIT binary patch delta 649 zcmY+CSx-|@6otRrUOI8L*9NT(LJScC9Yl-|Mh&KdiNPpnF|~;jW5h~8uNL}5B2PF^ z^*Ew9qT*~BS_`Oe81>nA|AGI&xLaeym$UXccipwt-sjBK{HR&<_1DZtAj&5vHr1{# zRX?51Y=h}q)yXM?)A%*KsqX#(E7jeRvXWBvdl=-bfsb=4>S}geFu2GiHSB89uBb1r zE&5fB+Kd@c_3p*44A+Xx>#Esp$>yfPEpDr6_oz0iVxBt3eS-%)R70MI>M@==d7@!j zsbu59bZ>9ZKx52GA3J35jBz#ZSzY~tS5993Pl;QpKB2tkjq;UmcDyslGNIz-N!J)T zgFNGt^W|;!p*ftCNvo})92+<20sS+2Hk@NPI7LLxPRufx2!`!>&J;LrCmzs!mkYGp zm?e`4hQg6ND&qF!xDn5aj-5tXt3_2baj+GGZJ4xC&vu&FK?^%sBVX4QeS<=3Vy$e8 z^ur|4N>v|6v6ZSL^oXc~W|j5&?IYY#-@KJwcS{i>1@37`+D8*SHjDz#%LsdOy!{)# zK-$KFTy?lbbeA~qmMX>)I;mt2D+OzbOPsx|C!t!+XvZW|5~f1wq?BNYtpM6k7#Zv2f+A zRs5V06%DNsSazSalz&ym(;MeNxP!HSuOfijfw%s=PM7NIGTthqHTwI z;~r8B%>TKr(WMSrmFm0Us2_!Op83oTo15HHU))35J(cuS8ux96c%TM7RsLZfn~Z2U z$)0HK;l6l0cB*zyvM+JO<_V)})>Gqu#tW0@|CdCPJ>63Bl2@vxsL^(mIok-)N+a7@Et$H7eym|FG>Fi|F`{Vs zsGH*yj2Aym(!tsIt_9#WUee9guN2=%T77rl<<`BwCF}&PyGQ{)r!#o diff --git a/bin/main/dev/loki/hook/Hook.class b/bin/main/dev/loki/hook/Hook.class index 42db737cfedd45080fb5201b0f6f708eeb799380..272eb9da51e4fefaf39b35c73678561b665b1a4f 100644 GIT binary patch literal 2461 zcmaJ?OHUhT6g`6t1~V~l90KG?h#8WYJevSb12!aXNgy$y3F*t&BQvq%L1P=WzaZ+e z%PzZUR*~`$q*52%R_br6s-FAJcx>EJy_oNH&%Ni|d%w?r{qy@D0G9Ag1RWX%3+8^Z zY?rL$u5Fi+_xXt+tfBu!ZamlU~r!UAFVN znq^lsjI7yCF}YJOm8@EF&D*9)Oq`pAY~9(n_H9SQi8Vj&UOD%J=_DIxwAS-=$1y9l zY%NzbHHKH(O z4^HE31ZOnFeZ@yr=r{*mLnOPobZ_O`D*`>QqZhi&F6cOhn9OE$^r2rv-lppl3}6H$hd~dMANLj9In7CI(iUg2CIGvWA(~W zFX5V%FGZ>MrjAc=i^Pg%EnRn1Z)DB9T`7wY9;2fjug@?QN{e8l^##bG$kF)H|57kOnGiJ z&W7paRfmvWy6f&UUJ(9B!>VQ~*_u-?7A4a>-4^a^Iv%vt?5c)Ndd#q`1{wE17o?Ym zjmyB`Gzq>X1GKx}c2Oo;g|yd(-9Sj{$Zd&Q)pfuBxmfnQpNq8D|I$#;bPP&E^;TIW z|6YU>s?(!cyUvuXS|YngNL?0LI748Fsp%$`9naaI^gG86X%IECb=AnmRU_M0jl2zN zWK@oQ^fZKFen+_ak@zsj^Tux&HD(VnW?VePxG{N%N#g@f9b(D|9b#HOiTBEl!S7iW zdXUf|h973Y5za<&0&$$eaZF(hv$Qi@=eUSVTrrFyuc?{P?@%)#7$Tv|NP4uM^F+?# zLTSzjGk7bTCXrD(1Sa^MWEM{H=AR;s(<=C=>%tEn$5qS15MG%c?OzhpbILH?z->-X}IpY|B1Rp$AWQy#LtLwnEMmq_r|jWOozXd#2#Sk zBX0B$?=EsWJ^l*@Wa|TN|B8t55sRWV#6*J0mAV_O;4IU7kwh-xCX+UgxfZ<(9*rrM z;xiUT!(F1f5sKk&82mSpi|(mUdFlrYm&Q_~+|2~XRNGzSyLg>dy3wL{z}LHiRnK9G z^%lKK7B##^WEl6}wuyW-{>tWd=`Af?X>OETqCq zJS8zVd0)InXZTI%jUS+kcA#=;O1E4w&+%Plqsit&tb6XH4+YRkcVq83=};ALy)3nO z>GYGZfky%0@pkZXAoww|0pPiIa4HbIiF5#XsvVpT1V8ap*s3`pnQPU&5(wT31W&eu zR|COc27;fqgYN}`xAEN5JWrm|a$UywA)YoH*R9okg?3+pF0WR!l^2+J4PPmpU;hgv Cb(G!! literal 2265 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 delta 487 zcmZvXJ5K^Z6ot>Qvx_XFVkEGxAO(bCSw(p$z6uj7|ALK#*!Zk$MKD5q6=r^cJ&BUI zg%)&L_;>7$cXo-v#Afcvy(ix}b1T}y{QQ0!0NBDt-z>3=4kH4Fq(B&BO3esFU@A2$ z5S6>^Ytm@cPa6A2Q7m9FjH0ZEa?F*TP>ZiJSPwBvX0?M_!9(0}JtUm82g}hs*nJ8L zAw{nZgWhv+Gzou!3A8YcHY{AqcWozW^SwWQgYtnZndgO+?YzKxgwA_fkCvJb3=AE4 z%qwZTs~p|aIP!;2wB2uETJ?c7QxECOD9(RnQo)~?nW^BZ?iHg@=VWF@Bg`K VsQQd$tk5(G%}~P{sdax}`vDO?Iw$}D diff --git a/bin/main/dev/loki/hook/HookRescue.class b/bin/main/dev/loki/hook/HookRescue.class index c1194238c34743f90494ac22d1c283ecd7ce0d7f..98c09848ec11938cc386a91cc4735779f6420fd2 100644 GIT binary patch literal 6796 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 P+SMjR)Ml;+DW&(nSmRfn literal 5418 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 U|1T}5gq3ncl?sjrxpMda0fgFdKL7v# diff --git a/bin/main/dev/loki/hook/HookSabotage$1.class b/bin/main/dev/loki/hook/HookSabotage$1.class new file mode 100644 index 0000000000000000000000000000000000000000..f14c4eeff05a2758c12e84445a45884118e3889c GIT binary patch literal 2656 zcmaJ?NmmqC6#gC{4NWPCqCpgif_67(NhTB9I51-xR2uN8iJ|Cz(q$J_ovLag8I2iC zHf9^$xkz%dNRFNZB(iXkg^R54FXVT4a`L_E23iK|oL6tS_ub*U!~5sI#U}vH;)f8* z1oo!ojBYtuQ_na~Rv+LmX-qo4F(q3&L#Plq=(tn*WImfUecelCWIAt2SMLfAM)J08 zOj=SPBrPN7$+UnN6sYtwrq_Cm;uD+8j|*rshSloj-5GPnaRv6Qa_1~#mTHnk8hNV} zZ$58QMP1^CF=Oa?-?a3xvB92Ys&YYK=c<$ypU6?G?x!+_J0(ZWRMw-FCr#V*PYGO$ zY;a{6hWILNtx;>uSmj(*JQy7psOWamvKq8aLkLxBwM#=a!fN%3h8k#UwMWBNY*VYf z8fsA)Mmd@_)M39sc_ccnD6eYRf=arRFl{-UpPrQNXdwjk2`6P(BZxI|73g2{Wf!!~{vrLF9Jp&pBaZagr?Ca*#3*}Nc()*2R*&R^hVI(jd z!ceL2>$o(W#|3Jo4FfcO(DtQkq?9QmhCNjPw|S+rYE`YxkSn6F_XgbxJ{*wWXtvyN-(MtX)xmv^P^%bUL5?M&w! zn)$HsnX+djhuA~92qYJMt7t26oSW&qYArL*ZKIt1N z=5#LT4=$noBx)HzoT_)!etMcrV*}RP|oE&}*kG$7586q@Qt!&d0c^(D4n| zU*;9q_Y%LNSJ=wC9LM*3*T@ybcwu~s&qMf3pfzanLA*?R#>vWJ78LTOk6G=wvnr9k z5csJyx<&4SzyZTfThePy(1m<1zCNc)PF}b;I-8RluwO0VKRzGGF?8rQJ#~Lf<(HLvkGLNR% zpV${GpGV81;Q9gnw*|^iU_at$W#2x?GmRoCi2F9q0*6Qql!ysb&`98q*c=)ba5&Na z6b}P-BgU3jfDVd6==>c|eQu-S>8W%7X=%>Zy zP~MKCf&bHl_Y9i(#zfJDBk1N^*UP7_kN;l22>oom1DN0{jdN@|2{;(Sml(z^cK17+ zf5b)lIfCDi#P8%*sKtm4ktw29g7!q1h1HQfe+uY#^>oDv=OGD;~!7)LFdViDqXl z@>cp-skD9VQ{VauD4|@G`~aRdKZL)4`c2|cSHwcKwR8FQ%y-T=GiP7Dd9eXt8sAum zF&u7)j^oLe>nusxa%QQS=QXK$Q)H(sBp8lM)pTlMtL17Zs4s~|=n3V_Yz1zHzRzo( zU>ML#Zjha%gw_5TiwuJu?qv-X!~R{6gc!K!`mVmra4xqCU`JeWmv_bdB157i8zKpY z4{YqgfElH1SQs2a9HTapNHfH92AZn6zL*Q!HK7)^t4LR6oqLO1xn|xCCQNAz0G`Cb?a88TpUIo zr!5pH;eXcKID=2=6qINa3&Y9W_DYZYP3PO%JyF-iZnECcARGqg`|?crZszr$>#tY; z%0z!`{rqA=>JpnC|Zv;X#O%RIbAt9pURx z2}g_j+WA4MHrLJ^Z;QqP1xnJB%Eo8-oc8Ye&FMP~lhyyXnrc6f3l_d)IM#P%Nw$}z zPfBcj1v=e?QNz&e-Hv&!T)$a-Fa2BHv~zt4w?SP}I>k%4Vu5Th+T*`%*V*_Q(+tU) zlDtvp0m(X*>!0{-CB=M&VQ5~4sxGd%#(zwXI^3cDfnl5E(xhU*j- z_eDL_V&^t8obB=Fvd35Gt1us`j@yx{uRHCE?+aD(cn}D>yMuEg(6cho$dFIt^j(UL zj2PPjBWYt=6QL)GA$mVdqYE@QxK!cv8tlTEHS8_?jgiM&zD0J#wi@Y zL7FifLb{u#Nofo&Us%Q7_)~mHy}gQYGkuEebL3*Uot}7t$!GZD87|)bmtf4O!!M-m uA^G`NjN>;Pr9Ow>dr0$L(gbD*9YYCa>J#MitC*#csmBN~zY0Au9Qg+>kceCW literal 0 HcmV?d00001 diff --git a/bin/main/dev/loki/hook/HookSabotage.class b/bin/main/dev/loki/hook/HookSabotage.class new file mode 100644 index 0000000000000000000000000000000000000000..0bf6cf5d4afd73107c22176c385bb981f98aaf9d GIT binary patch literal 5419 zcmb7H349dQ8UIg`&4kS;Ap}f7ASh}QlB@_`ECtDd32uTVArP$6$?hZ>vzb|DW~1q0 z(bjs^vsw#n?S0!8C<%lpwHLNl+fDD%+urwmqd)&|W{=q%v`v1$eQ)0T-uJ%a{~hm{ z7muF+uo*APD3Guurj3N+=8zs5G|iz<8-KdgKGRVLG#Nz_%5GFg)KFYC2148VZq%ZV zgyKXzIiMRn8!@ecP+xLrNOwZsV(40TFHEYaY4qy@63&feuke;aSF6KXv%5>WQgLsG zYVbl!!qOaZR-W`GY0K=iXw8GFMG?q-e#8|p>FDuLhngVKye@sfP@SZuNodO7-QfG& zv7@~u>{q~!4r;MvT(d$=u14LeJrv$ZhG$t?)Ev>QQL$oEvkkhTI~yf**Z9xp8z860 zOr>*sGjy!&kxsnV|Da(AEq`cSZ?qp-H$D>#;!w)vlb2M#0NKe3X)@dt|G?~z~m|!iTJ`dJeIa_LTPR(+q^bvH*=%9;c-lYCLsp>bBHab=RQY?^LIbq$a2Cub#R!bue`(b%HwwNpY|`b$n{y z)P1Srse_G;jq@;sVHt4=jfUoghDJ?G52>*ch71#M$kA?gLj9&StU94iX8l+<7n;qu zX(=#ZGMDzYMk3qj?~5b<-+6}NUf42d>GS41*=!CcOoOs0aFAqX74k?J@Efwru~(i)!ZPkrg^Dib08;PMo6LW>G&K zu2e1Q=E<_@z?C2+rd8_vbjG6s(h@3i>~k0wc~sO|ty_ytYRK%zX;( z#{-0wr46%WOSsVQYOjHz|6CFLrJxjd;UNWY7S*;msv1!(9>gPfRK{Cab$!x#t$`>R zZ*>!8zA|L(qs_Oo!0Q18Z^PRq1hnCVLr~PQB+LoknOJ&E!8-+(&JtMK>}LNal>wk+ zFyhrpMYq$Iayz71Y_(h9YB1@<%$p2hw)ZRe06s|7N7Q&yqnj)9$}Iyn&d!g;CS1V9AdF&o@iC0 zgX~uP-kr(LbM+?`91-pcMB^-DY-qHbwP@1Oav)jb%l*>>W;U#gbRNMm1rs>#2CS1Q zOvHT6?%JLbJi*eFPMHV1EGY8hWa>of31-J9SBi9aLcym*Y71Pwy|uMdk7VsZ&HLl%T!}g)h3Mo$E4UU z+JtW__>O23miXF)?ddi>-7d)Z?wK2fOM*Cs?~6v^2NKr$z>-aW87%o4h3p=)vpD}F z1wY2~x#}NL*_lb$c&6%~mnCM)cWzhpQw2X0O>lrJwi7bBXNZ@IOES+AHdu6x71g%r zqK{wD#&n*Myio)k5zu6sj_p`#qC;~A&6pj;Z}EE>zhm{xB}3g9F^9BJL>=ynsU%eU z*kBo@n+>(T6{h2j=(qrDH8nL)$uZmEPWFp;4x!E>TYiC^P9qb-WXnyiM zu4mlzt~~Quf7(xTZdT**F5S_>^NaDDIUWf;W%%>_d3aF5syv_0>bF`_|BYa1Hw?{c zj;pqtLtQk}(l0nX+=--*8OV$RF3fWIQk-%V~s9P8?iVqx7R7WW=S8pg2lkjwXc{x0XuzsjxHBPnAy+R#jVG5BC2zs~(beOq?Wj9~s`?3p3a}r6!pFIGX=lUJV#=MB4HLMM zRrUy0jAPR{TGtm<6`jPws-kgRa{#4ce7SqhBk3-y7`!YniCw*QtLv+Z76p!CH+Pl= zgX6etQl+jv7n)R@Gx#XhQtI$3Q+VoMJuGbz*g6cjZ`ay3amjD zE};?Daqm)=+;#lDjOFGEz9870^U-5++{>?9(2F~;n+~`R_i}s$*SiL5^VH57>`}Z5 zuclD~cnGh-YiY15p1lRH~{6)qI z2+CN08g()v(hF#lWcET5?!lWpgX1xB7C=}h^j|%JdkgRs?&_#NiB+^b<6`wOJlxrE z5{oi6Q5ue6{{-Gq009Zl8cSSA_7Qeb+9ifcXjn$Q*J98Ihjm%)*5O^$iS1%q>H<

C8PYaW5ATi)nw*!wZq1<@XX17CmpfqKFXW zC23I#FotnY0O@j`D(2pUt~P|bYy6${gzZre=_j#1WArsyqc@aK9>$XyL~{Qr3H)Uw z9LDFA@@FRSB_@d}54FeewVht?2ri_1Dha;}2)#yvZv%nXMZoPK;C6A`LtqUN)(!!C3&(wU7!MHM ztH@!ar)bt652nFQ}=^6Bh?NIgFH*}LbQd+#~#d(V3(=02F| zxN`H%IRFdrTess6AtYl4W=WXIkYWTHytQpjO^rcssjo$=F#MW=*_gv1S1&Iqs##F8 ztcoGE{0{qOIiX^ng85j$;B3``w-PXn&c5S;NvrHAVrW{wws>LjI@LT7+oPJ@u{~y= z8Bxviu?{n0_M1Ji?PibJA72ZrW-s|2u^nbOwliNf51CzNx7iolXPzP}O7R}mJZ*NV z=2^2Vwj;KiPM*z|QG!wlix~3xW_v0(MJqpUOSLIjj3s=#ZN6NF3JK*58H4L1?p08U zWenD!wl&DmkX^pf*Y5K+`}78HbuiGVH*hAUB{HRpo{2TItH@~CWaye6OkBhY1y!g{ zT!ikg&{|u44H`coPjaqufMKPARanh$3VmWN{1WQ;c}to_Lp{H4ne48`Mg>i%r%N~L ztF<~q_qRIH0z-n%Fo7^mAncp4nO9i@$*l~Q-#ZUGQHHiS>UM6m&2u`j6^~1JjA7y} z&g1@rQo%Mn!C{S7EdTRi7q}@E^&u(O2VfM$;0bw zfp#sRpa;GDsJwyoaf`zvo>OohpYdsqDdGiuDd7v=;<(516)y6<4iEd9M;)`p@9=|! z@A+SjN%oU489KAexmOv_mpZpuzqR8>e%V>Te&$TcVZU;ZazVZX3#|0;%K4v4Mkwzh zGV`KvGa@`TFO0${<_gGjEexYLf~8R`7m$m9}L^-5l6nRt_4`L1JFK&e%!deKFsuT}n9Tj+l!ZL)vktRXS zr=={)LJFqvHQE->&b)@N;sYfc2wpP%V!1T z=0>rXA&i$4*Q-&44%%*q-cVd`MM04`S0TZ53KX(E5Rpe#KJm>Z7E_4j1DK4dxSz%} z6wueHkW@bd8!&SiXWtNKUxFT^vt{&V-lpu+DC;hS4ije~{tgF8_LFa;lG9yBD(KqB zR8oT+1pOU`3eQYy&MDm8i}%M^&)^ea7~v?o1+4ymljcyixv*m%fuB!=iU>e4#?b@H zq852k93mWx6Nsk{lpR7meXvUwVtSe?36v&(ut}px`%#Kn6YX+>{eu(Y^HMT~I( delta 1200 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 Ifq$X-e{iQJ`v3p{ diff --git a/bin/main/dev/loki/killer/GeneratorKick$1.class b/bin/main/dev/loki/killer/GeneratorKick$1.class new file mode 100644 index 0000000000000000000000000000000000000000..4cb743ac368610ce84ec9a521bb61d0baac161ae GIT binary patch literal 2693 zcmaJ@TUQfT6#fpu1j1M`h6)Op7q!+2iS_Kr)04f(N*2|C_!;oPnotaSS)z-G! zm%eqa)+c@F>Py#ZT?M?X)jssWhd$w7=^C~vQJ zpuAq~%%SL{Ky9C$mh}*bXb7QBjW%hhM_7$EYiNL`Mq4zjM=*>4wrO|?+XVt0(Mh%Q zvW9gC(*LAk$&virv~LGh!=Jf4TzyDgieOEmW+md*e_6LxTBWI zp!`^;l;Qym-D)drxMP;ovxb#X4!*A84ZO*0(}{_^K{`d~!mK{8$Mc?H#wR9*`V(RF z;7|x}5wTJv)k4>B7z|!>Jv-N@=)gk+nmYb(FFF|}`fqDEs&{Q_2(D zx@OSj*79M6rTBn8C;R+eqLIW%2*U!~N@zSMrC}6jXox7Tt8MC{cuu`V& zGD*YIo3RPj2e_`l@^r^5m+AI3LybCU5|ykin&{I_bIkB$VnYxg*5Pwrpnv|9`uonkohx@&eqDL+^v2VhPj9a*t^B_7 zaAoQ6PQh-j4OwhXeWvcZ(hcED_NM0$s1{QQU-K?~u_#$n0DwJwgvC8*yB>D3Sq4~| z>-;AOG&ZXHj_-)N?ff_II{(d^j#4#zUcv_IHF9*8bEUf@*13d@v92XF#r7?sIo4de zgqGL?Y>fq$(0bojZ1Yvsaf8^(w%Eq^4$d?hqX{~*i*HsrwinEc%c%9uFUA(pw2a-! z&L?Q@e1xdL570P>3*5nG?rtndyta&kv1J^&ua*|^4n_I|3X_3loLn;AY+_E>}eD+jl^xMZ^KYNHvkC3WSW|d6Ky|Nuno0pOCU0^H$ z#t4j{iNC{yb%gU#bm0sR5V~$Q+@qKz3KQ)6=kYGT*Lc7zrjW%YBB`+N-q sh^@=L8hAVlwAh1rEO1muOP}$%fiF0VP(o!$eQwh3EoSdF$2I8r7eegjiU0rr literal 0 HcmV?d00001 diff --git a/bin/main/dev/loki/killer/GeneratorKick$2.class b/bin/main/dev/loki/killer/GeneratorKick$2.class new file mode 100644 index 0000000000000000000000000000000000000000..01e4fd61d2d5c82614c8c6062e6344db03f4ffa6 GIT binary patch literal 838 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 literal 0 HcmV?d00001 diff --git a/bin/main/dev/loki/killer/KillerCarry$CarryData.class b/bin/main/dev/loki/killer/KillerCarry$CarryData.class index 7b8ff94565b413576f002c2b006488593003eb63..fb49924047b103d112e439013d2975d9db1e3732 100644 GIT binary patch delta 29 kcmbQvI-PZc9h0CI0}F#T11EzH10RDfgD`{MWPc_P083H?Z~y=R delta 29 kcmbQvI-PZc9h0Cm0}F!;11Ezl10RDNgD`{qWPc_P07ruaB>(^b diff --git a/bin/main/dev/loki/killer/KillerCarry.class b/bin/main/dev/loki/killer/KillerCarry.class index 62ae9012283d6849a3bcc926b39d5d2a10e637dd..00fdac545aa6d1c6b378024de97f1c70cba217c0 100644 GIT binary patch literal 9633 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 VxtHj?PacqmP$>^{+)wW|{2r7{#>xNy literal 6731 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 F{|6AlMz#O| diff --git a/bin/main/dev/loki/killer/KillerController.class b/bin/main/dev/loki/killer/KillerController.class index 9ab4c7f58f6b65402af7a81732f8c8283598f86c..216eb72888299c10ef442290bfbf3015b8ada5b9 100644 GIT binary patch delta 1329 zcma)5Nla5w6g|JC|CiqXJT|= zq8npkq9mqK7siEdG;yPAH*DOvaPM?q`%4=QiJLdvckemp4)1&JhfMYTuUD@DRN(fE zdf#YKQGzlBr4pReXPhR6a%|$ilJeyWR4UlQ-KIsxDpd0ZQ*Kr*>J`*U&<=V>y@ey8 zK!0I<$QSg627?R@Xyj9-EUB5#n4EGeLmS%pXVWTkEjk#sp_4PYUCTxAVbCL5b!)c65Py@r2-e7PnFyI$t=NZnCN4L2y=MuvOOiHl%eIZZ4@9+1u1qb~> z-|(<-yvlGHW82NJsI zW`p53tS~BcSA3`y7D-f(RtLQ|(Js+$C*Hg;dT4Wp(I-@du|seh61ZJZuo5Y7BMpTS zZWfK!xh2s8{%t|JYj?TxE-ZR7dz?=QC^3mOP_dSP){*!Pe07RrFe({HhY$OSRHaux z4p5XxLx%JNb|ID8Nvn{4jNU{+(uL*%R~QEqIs5H}VGy9o10)2_tCNWwwxqmD(s>b7Fz?Gv^6B^OVn8^X8O^zq;GzFibq*Zd=?;|2AU2_>7Mwn8k+`-dCRT yw+ZHJxXvrI9G zw&}j_OVWKw(2G>zcl#CoSF1CeJTiGt!=d z)`C^anzn-7DaXww7fQLD=_P~7+g5=M?OX?CuKsn7>ZT zO=;bAmlgCkFr1QQ+O_4KB_nUxlw+WQ>2zp8>(0P`$}D<@EumHI2}27Qlr4nw+7YEaQ8 zzXmk4BPPG@*U$k?#SjfEIjP*KDC#>bXE26C7?mxDHEczd1BYb74VmW{IG_P;0n8D(bHY;g3JD9+H8e=Ym*jinuyN}rv(jZWE7~2 zdU3{g&r?kJc^T)rE$O?aAty~DRya14Ic|UeL}-0#ZyPAkkLJ3 zu3#9VB&|=lPGMc`$vKYCX!tB%X5KTtx#rk`wC5bTPB)i7Q-;38zIzB?)bJ(orxiV? z5T&@YtZ%TJmvkg~c}g>j=j|!Sv4Zpr;_Di|D!#W-jPm@f>ttO&>UFB-gsA;(4d1{w zrA-z6Hq26!fWD{UI|3?4OXf|{<8Z^A#v7+V&|cN>ef&VdR-#=rvsue16O`2-Yxtp1 zXy?(#X`6XF`|3#tX{@*qkS}wBq*)hO0vg9>Q4v|2C7mOe5;qyZP4d5k$AMdTas^ZTmcPDLoRsBNoV|_n3Z`Gj zj?vrrkb=MAxfEB%53J%cXLxvCeg9t)PctN*`+cnk`TQJWRD+CTKYtDLaWz6l!#IzG zAInak?Q9s!BzGCON|sq-Y(>-i47OS2x5`_V6N=n>VGucv67-O8{X1eZtit3lyb6oM z*eYxe%SR7bE{m4cy>V3&$RD zbzpQA?qvTf=nz*_!|Q*;-y|w@`*x2o6OYo_V^rdZ?<5iIuVk<%jGCCZPW&eB=)^K! zpeLcw3gzF}DdFw5L{zQ!MV&EQ&MK{hUu?AlP1!Vpk|ddniCtB)|?4;B&YVx;{qNQtIuY@Ry_0 z6!RBmSWV}BCqb39SDdtmP858;0yh)Fb%$_YC`WWL!tM0QK26*?7UP8~?CuJ7cLn>) z73>YEUL@o*RdDsFex(9eP1P6uVwrO-)#s@9^Q3yY3cDUvzG6aEk5;KV%!GbTQrP65 zX;vYBU#WtsN0U#Q22kI6vj!@&0TiD>b)Y2T@7A(f+yIJipaxK{yj8=>+5n0VrUpT diff --git a/bin/main/dev/loki/listener/PlayerInteractListener.class b/bin/main/dev/loki/listener/PlayerInteractListener.class index 0144bc296a9cf7ae274c6cb882f30d6e1518a779..cf58c1e6271d87a9d40ed39c9876e56d98403100 100644 GIT binary patch literal 9278 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 literal 8758 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 qPr17#$F5LVH-{z9S=CooVng0U~UC$=~ diff --git a/bin/main/dev/loki/listener/PlayerJumpListener.class b/bin/main/dev/loki/listener/PlayerJumpListener.class new file mode 100644 index 0000000000000000000000000000000000000000..01b4ba11413cabb2eeda8a8c0c0739b97509f020 GIT binary patch literal 2436 zcma)7+fv(B6kP{{1-1wYcS8~&P{4-blC)`3)1(v0=-IO`T5Hkqk(MFl7e$T=rReueJBt=dXW$`vbrX_e?AQ+_y`f>Dc)h$FBMZ(xqY3 znpw6D&v&K3{mX2pqV5=y=^fQb6&?^L<;t=m=E@y`mI%$k45AqqHy38Rv0SIpNZHlBG0q7}qc+(EYY5((x`P zNT)2lHR&EoSD<4w8_i6(Tu9?R+}1Ej`6F22rj9#!pJh&vur`+r+bAon!8led%QYh+ z-^D!*9}4tEkS{XTaUUNETsh(QNZlG{?Ov>u;&-VK0OSd5Bj}ydS0U44WFhh!=OvM#mPuWRA`1iaqB#zRj-1F0!SLey!shd@GP< z(H5oUlmg;CagRYmEwW)dZXpE&B@NrWco9Ys3mrRP&gu!c8Tgq^K0N+O{k=%T#J+|- zf!?$1pX(GlEF7?_8>N!0c`w{)H}Ko4x-JW#PPHqprGv4~b@t`c zc?)gXFK~_DR$6TEWF3Dya&OR<`yHK|Z_xb;ncUbb^u7!rZt&ZRdVVwfcA*75Xh$z@ za-}moj6U>pMwlQ7LGS_G;$9fUVBl(%bLA?ju!j_O0t!17tlnf`eG$y$DP|JG0ihAv z>O>|2SJX&glpaGObAb?yG4~64kC7EPMqVHnPCrQCj7;e87P>JQI1E8EHHQL)Q_uy5 zrX05Ae#KNyFl@qQj`698z=hfjQ=1V=KT59I2yELa>>TC^w?Md7f)nhSPv?RPa56a(TW9b$_Ep!?S*FRk=_H?_Zp! BX$=4X literal 0 HcmV?d00001 diff --git a/bin/main/dev/loki/objective/Generator.class b/bin/main/dev/loki/objective/Generator.class index e9bc4dfed9412fc4392bc9f0f306d0f34975bf04..8ef29ffea55d9b152c2d83e12320723183a4ed03 100644 GIT binary patch delta 330 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 L|3US6_~-utAb!cx diff --git a/bin/main/dev/loki/objective/GeneratorRegression.class b/bin/main/dev/loki/objective/GeneratorRegression.class index 5675bb5fdb6bf8b1c19716cf9f46a3a7a597c790..82d411bbdcf54d3e3341e5041904350b0f5fe108 100644 GIT binary patch literal 2300 zcma)7ZBrXn6n-uVYzQ|YKzJ!o3RK$g(uNkSFVtEJLb1G5ps3WhCAkd?$!^?TP(S%I zoHFXye(8+U8O5m|96$RX{43VyZk8k{!M&b$_N}b5v!`;tb*Qq7%2uxFJMOwFxXSY^$2JfZNbQ-2X0~YBh3s13EYOs5>?c-1 zpntgQf9p1SWGRao6TrPf7D_UUa{M9p~ zTXA%u$3Qn3s1k4nj>HA@3ba_>oKq?nm9O$aVcU9rzr;mtZMD36%CZaVs%%=W%4^#t zi9u~^{>tUt>wIK`Xq#u&1=6a5?8ffH~m%}w`3MiduoJH zi7||mURRZzL-o*gf~K<(nR(rh=E8{_1O{r&JEKTa7^QBAFXjaj8@`#_Uop#JKiV9- zDy)-ArtrFfX@TAf-it2)&RG-kG! z0#W&7NqgUscvth;W_kZ|)_l!MbRez$<|O8|_&V|`M`D!i*O=4x1&KwSS3C2nSvHB{ zY4eiA`*=X|m{g@$&F8jG#n7qD7K@bT`RZCRZL^@XF_dO?Rhx2J)xfhPjWtUFV>fr()f@ZvKz`AMD>RC0vcu zLY&(6iElUuZcZ&&`h@DKwxK4L>zXbMvHS=#A$|II<1EIx5!{>kw15Wwnd#WCyvBb) z;su`?(8_x*dDA)aGu04?^lVbQ3r!eGF8+JCUA{t!>s5!vdn3c zUi^wAk(i6naHE>ceTLKprpBXVKO=FBo2m5fG2X0|84v6ogiF_zn5u%MLNK946qZ=# z8Xa|jHNA7h+QvA!%j+Jm_kKWZJaUZtwZeJ>>bpsM4-!1A+tABd9}nh>b(jNH%z+S7 zm&N>J8h05hA^P{k)&x%zZ1p8J1y-M7@Y+jk3;cnum5G4&Rt|kfh>dalg2o+zqgQ{` za_VC1Kx(@wk`Yol#`zRyHNvN>`AkFM3DLXR<^rj&5S~WEE0z;AAPlUL|HnW-ZTu*N znciZaf+(`W{calj&QE-<2&^fvWdcN5@e2_f^atqd7^SmfOq>y;88)0S#&(!TA5}+5 xc5)y!Ne<=YcR!*r`aJTSY1ON6lJ(r64sWvL$-3};L2D2wj#9fo1TUz_{|AtW;bZ^+ literal 2679 zcma)8ZBrXn6n<`4vLq~|&`_YIX$zKyq;0Cj)|%E92*pTvF;G-|!6n&-ElD;e8`}D= zRja?iflPbIx;~b8r6o=Z`-DjNw}q z9)aPku@TE#>t@Vay=7!HNiok(e`i36M>&0Ab&M_A7 zW~|~(Gbb>RsynJp#isOvG2yJn1rE>7rDw)cE9uFp^yI=qa&C5IeloqXn4Fjv5D9?} znpSMRXfNscs^Pu*-KKoD1p20Fv)4ZF)hEn@52Ltj7ESvVfn(wQ!WdZ+P$sOb;YSM& zY6u`G&=MY5^3lg34XyAB1X5#u7_%dol`)-#~0L>tTJsmlF@HKP~xoKbf4wS+fS zydf}Dr(zH4-3=Pv!ZMEmf)l1rIrHrfkEEQ#p=z7?Sjw!}@pim}_f)*g((hi;u!8pm z+DyB4$guTx?M}M$^stJoiVWR1JnXD$FmRJZ7mYi%Kyb%H)8Q1C)UIim!hrO2OT)S> zfx4zwW}QuHp~i#2$$f>lLlfo>Dr;6XpQT{S zO$GZOt?#t%9SwISPCIcLozu(HC{xW7ts#MwRbd&t;Y4y-_U0$}OvR@=)H|fi=r+mt z9A7Yvf=MB_h7*Yqp1f_PdQS$*^#k7(vPklHX3hNzR<)cluAA~e?5mf)5x`klA|6BZ zP6ArcEl*SaIt&$Uob~ay2Y$Y5P469?XOrOozq@F0oi-kh{>X3WjzoV&&m(7FFTXn- z&+Sm?!G{j?aqdvf8vXPuAHRdjkYz);KJRg#!nJU88%MX`=PO(Jb$iGMz@c!8&Nr1r<`{Zn`#o zOYb4(_v#k&jPS=aKQsKy|A@9|3sD_z$Iu0yAT)E@sY8In&=%Sv9CKVW1-734 z-H5A3ew2kc&1lXb!aM0a&NY!gT90QGc@&7=OQwn}Lyi0(JWrX9qC%)xVuGt6OCEV= zxshIAoPv>RwHjDKl&zon?s3ZRb=J;1h+56gMfAw~ac>{?UOq1F;lqb2He4UeZX8Fb zLQ1rU1F7lcaJ%#SpU|p2YV4r)KXq)6n`k9+va evFWEszUHm~yH6@#aj;wA@sCU0e?wyr?*9vUB`-Mu diff --git a/bin/main/dev/loki/objective/GeneratorRepair$RepairSession.class b/bin/main/dev/loki/objective/GeneratorRepair$RepairSession.class index 71881c8dc16824ff412c826fc79a7cf83aaaa8c8..7b8a34ad949035af9b9a5c5ead8c7cf0ecb960cc 100644 GIT binary patch delta 29 lcmX@ddX9BNDU;xJ1{Q`J44e!%8Tc4(F^DkSp4`Ud0RV{`2(ADC delta 29 lcmX@ddX9BNDU;xK1{Q`L44e!*8Tc4>F^Dkip4`Ud0RVve2o3-M diff --git a/bin/main/dev/loki/objective/GeneratorRepair.class b/bin/main/dev/loki/objective/GeneratorRepair.class index a376b344955ce6d3b11ebff6c1e7a4e5263d857a..d19b18b2f4f2838f5726cfe73692936429c9f411 100644 GIT binary patch delta 3393 zcmai0X>?TO8GfE@H_N32k`N{)VIUC_Fb)-JB>|MJH4+3#00|(SWbP!xOlH!VNiZl5 zgtCdEazP8UAZ~!#N`QF`_nrr(=Yt@L??h5{Fj0_V$2gCjcL2ZJ$ww86T<^qaFN%c`4?(+ek~Ai`MgA8 zQFVFo;$+^ZUmAXEC>7*)RN66<$oBezE@#VJm&c>|m$-uUvwRJW_CSD0ZuHn&G=I5+ zPy$W0wW5n)bt;{#S6D|3acIppkI(J0H8^SwbyBWVsue) zMGv&r=MBlFtskZ=l^%K(HV}DzkzWC-^s!O=5hLHWH)urub-E1+<0htF^!gc8D30g! z>J6Fh^?|8u;xxiyx^6~4yUA}43Uig3`0EIyp_Hpt-j7X0yr5Ds8>dq_hYzTH5KqUV zp6~UzyxIbjv_LW+BATZt;r=n+~N4nNqyEa5mM30DgdsnM9dOCW< z8a)04U)%#4Reaxs@1MG$5pSu>snq@)15t zqzQW=!-;uX&`yk%^!3r(>t};po6Z`zGKG&3Hx3T$u2}<{#_fDUZ+!41^G}`kRlP zi^U)Fh{9KhVSU1jU8-#3QAwDgrM6ykG|9C33Uh&LH@2B(-S;5Nh9jQGRkkxM4XpT^ zWF#&q($6S4p@GYL}HqmyD^x(dU~Z>zJ1nN^;)faw3W&HK<~`VGdF3*g( zGUGTqp{k`((;v#TW?fE4uJK*|ROL_kv!O}+lN{(7SvMzqE~C#-ky=>c^SPTE75+nS z$lAuC`oFU(vTi^Ea*={Od=2@qU=+r}s#lLVH>CgyZbYWMTQCke7?07IfScr30vm3} zMBIh1V=g9R0ltA{xE1x7g8NabFUfv6Z<_kDw0Qu?oB3#$Ghw8F+C>ZeN67pOmxL(kvma#&B_QBw8fsH7Jm03D)YL=IqE? zhh(hBP_!Zg8zkIT6k?;k{f5``wjd2#F$@n2N!xI<*!dfjN=qvMzQa(exL<3aokLOJYO)SY|N|=jF5{AXy8|G3=LI=DVToFNY z93r?U4&yBeVcc%4T-hda@!}zH3shP6Aqo3scL3=UT{fOYF`kozJTLsbfHE8u5)Yvg zhlRD5#8XIeOxu#MiSmXJs#zl|Wnax&IuK{<@SqSFFUqTgln10DTIDnq@&AFUVDhM& z;tM zVYZYebcDDjf&)Zp(m@=uBt>wL0-Ic#Xh}5XV$xwO+YLc%vLu@9tnFpzBtuJ&a{a5^ zT&h@jRDElPyX z3*BZAha>zE&Albjl5&E_fZUA{zM6<7hL^{PJy)xGvr&x^Lcl9h6Kzr@MBN&EoRI{cl|;QE@N=@hi4Soem*x41oP3VA&9p6- zjueKkr43hjTJj(>%-9Bx`fAo39HlQH*MQgXg5fwqhCI zlcS#6)EfUBHD{!N{nHGs`S({BeC#{zzWb_vep_zsPs=*YdNoV})`=!i_U4J;^N2 zcl9gzxuNt_W0-ZTDd|=-WW1Tq31<3?ob;7DZ_;NJbpqct?igj4JoDxKSK`&L&Gk3t u`XN7&&-x$v8UM_`2qs6!bPDFL!q68O%fDqJg@5N2{u35{VW<>Ooc{xSn8#cI delta 954 zcmXAnT}<0$6vlt2{g1YQ6BsyJXz4n(6u@(3bfKm`B-6$ z$>P|QsN1V?2}a!f$kZin(GXbTG{zgfs@{30iSgQaZ5B`Qy*Tg5Ip=xa^PKapcK_B= z^z)+!4}n&>?7|_0uKy9bzb8@Bz}tc+ZI5>h#}f8HKF`YOLU~!v2qhZrvg2cRIz5~k zJ+Aq*V1?Uidfd`yzAM@172M~NeHnYKZ9s6PCt@+J7374u>+LTpph<=cWrQn&Gj5NY zUf=$_l)UPabLOV6Zo`;xGi;=y-2CpFKQQW+3E_$6jGauTrdqla!^vdu@Q@u#Yzc~6 zri9JS@v**4OLjT?@Rx<`YWCaggY3PgTry*pD;mwC%6hqIBK}(Ik`OSv`~j2kkH}-@ zPyf-faxA=f@Uf8!^(xtp-y8}2xIc)C5T$I@Xtm~du!S%?sUf2FpxSoo>0mc6u!oa8 z#Sr^APlGvBb=Tjd=%*>;fTCLz`3!ZmYRzGqIbsTepO-zS746E{!7h%{sD3NYfBjR? zlFlwR&`mxsDnSoHUQ%FGfhV-^Bwd{1IA_#5i%lOX`k5uhA_KfdocFYmB}TYSf;%Mn zf|Ln_mb__|I8F%@lryP9r&QXc%9u94hHjM2;^rccGsh+_soXggJ#R8wfAGIf0aqwy z0Y6t&@--TGgErpeG>ax$y-@s)Vy|n}4FY=a;9c`~b+5c{+P8&0f8kU$5H&N~>gMVr zrQ%tUo`|&~y^&%^bVW{ityM-h$=Nj~9jwvmpw?@xaPYpOoZ25c-a@rY-M}%YsROo3~FXZKjlAcmkW4g QR_5e2ypn6oQqsZy0EVBzKL7v# diff --git a/bin/main/dev/loki/phase/EndgamePhase$1.class b/bin/main/dev/loki/phase/EndgamePhase$1.class index 75e938095229414f3877a01d3cf24bcdf3caad61..3cd726c9eb70b9d734ae4d213aa55c6d3c908c60 100644 GIT binary patch delta 23 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 literal 8421 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 literal 18754 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 diff --git a/bin/main/dev/loki/phase/LobbyPhase.class b/bin/main/dev/loki/phase/LobbyPhase.class index 2f408529c7724c43a8effa82cd5b2c81bb08cb21..8462a0407eb2467a394eb5add0512f5dec65e1bd 100644 GIT binary patch literal 5811 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 W>-(nl{R95QwK7uwjK7lJJpTnv`vcGb literal 5285 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 E0Sa|HHUIzs literal 3415 zcmb7F+jA3T6#so~l5Nv1NGYW$0#y{+v=HT{ZLkGeE-j&jHWUFBx5-+TrklFiRO|gN z-e!DLad3R&QJm2#J~%$fjDHiO{?50#woFI!u=y_M{Lbxn&iB{fzx@v27``yjpwOMQ zmlC=Bypt&8i*7cNwmmQJO<7r|SeTX*10jW7d2cQ;Tb!SF{6ryh&CV8cwwE}r52lK) zYt80th1T?msj11St5e5D$1Y4Os8ODckb9HQjIy1*o z6dn%;S7=v=Ear-Hj!VO-8q2|9;<~kDCC2$Vr-hOVruO};DkvRD>6+q^ zc_&Bz6*{Yei#cnVA)V1TlxZ#$y(MQU?-dj}QrcwEcXEl5JXJHklXpdE+L?1Lzvwae z{SOp6vc|%62{$<;o*Z#p#~)G{>G=78k2 zvUi%;fQ_|O&CnH zVEbvyo3njtJlRuKsn6{L^OO|#&;CE1b#oiU9XHX2%@TLNiFRy}TGmCmyjYGjOqP0n zl%h|VcoO@`DzE6Kt-^fTSrD=eKd3|{3XYHW*4A%ziwFu66e{@nMX_+m#9=8A*7^C7_YFk9<+QxyfK_HFs{)4pjI)F!dda$ zw>`_xGnpL(s!EQP&WSUL^9If-w5`20F$GSQkZZr{(@+^9XSs8U$=U1dN~Z4uLt34_ za?3@LMp8b{nz)3^bcn%C`+F7wV9K|d+O*0CcYjR#rp$Qu=(W9(G9+DIcqmFN7xn%EX6xVRwfTPe=aiwM; zr0gX-S1>UT63cwa%P)*pFuQw3M{D9-qs1^4ToZXLG74c|(CB*7C=ArOR7aO&HG6AO zduODuo{55F$GBz{CUgzWU=Wun+f~~`_2}ewAj>=`Y^fEjWQN%*4{fHV>W_rM(OzbO zo>UydWvhlLY1nX1QpeV*>aLB2FBch+nLleS>KPfuJ9y8)yS2oq(JH+lcvh63nrM1v~4I*#W37U4<-x(P~20 z3s>fHl;!s__DM$``HosQdEsXFNvq-!GN+L*cm!Pt^UFGO*MKJOqiCt@Wg_z};$54( zL|TS3TK{uGQ?5g=w?f8tCu00|@#GS*1b<`v7i^37{fr&){-4qPqZWRY-!_E!-O6t# zRbw>Lg}YO>xq`&SYIsq~ zgVtMVO~MM0Hk9ls9H<~2Afng}$5(K0HNbA>XHN}4xB~E089;qcnJ59ir6;|AqE-G@ z@bpcDehijFslI#M^xegAzS=zF>a@aO=r*p#LU%Et@CVWpdsi@X6Or2(=j!4Lu6!e# zXSjKBFdPftlU*Vf)+mFK{#axM)_~Dzw4>?kTiBu(zhZ8%IUWngnpdzO*e@&G{O4A+ z7AG}_`uOc<#uLot6RgO7mh>QoaflDoVa(uZj_(0vk;DQsUBnRH;_VGSn_p7yCZ9ve z=@i+@uuCcF!n^P(EA%L039qmM!#IyuvCR5EN4?j09wy>0yw2MQciT9|BV_&!BE5k( zNyN8^IEkiBxDV67$Uh`Q*nr~F;DccB4)tdS()!fAk5;YFw?=`Ea6*@elcF=y7*W=t zyRUx*Z?CTTNxE@L2N?81v_jG-KF|SDTe~a0@F6}bA@9=^1wt&y546yr$uF%9jMs1| zRN>Ia3_PIoMRG{IYTHBQuf9WA-H{c`d55R9Z8X7DP162(jo3je(#z#;ZPN)=I*pA` z@;-fnPfJGLA&@kEtWPtIj3l)t_6j%Sdzp!aDor|ADBuEZU1SSAOAD7dC$4Cl@c^V^ jGp0){QN~JHX=S@frW&;$&1h)4k7gE!etbr;27LZ6Y7`Qd diff --git a/bin/main/dev/loki/survivor/SurvivorController.class b/bin/main/dev/loki/survivor/SurvivorController.class index ca4925bf9edaa8bfc1ab9b5c9e4c020858636601..20d29e6fba066570ddec481784dc9d779350ee47 100644 GIT binary patch literal 2907 zcma)7TT|O+5Ir9@7#k68g%AQU;Z|c4l=PmEOQ|t|)Ib_@3F%eX3JA-RBg@Y4hx(L< zrc<0rJJZ+B^fz@n-Iau;F}9~KSo*#_=j@(cEr0*}`=0<-QH!8UV6LFnWZf)ka;0k3 zw3=zjIqxBD8n$KXx@tub78pELYKp8Y#<9F}bgJfUf!+&@z(lq|RMwR<)u&mZxRO~@ zOR7PTIX~_K+z{OIq6iT=Q59V?js<2r05;qpito9v8Jhi2;BLZ?Z%)L&$(zPY?O1-S zlvJvc9#jF31>v&biFo7wSxgn7GpE8{t;1s4ML=h7h$ZCeVT`e7{)~<4- zt7Jmf%q#l7Vrl&D9fs`_ts=1C6L+eT`s6wKB^Xrnl01pY4(}?)EP~3o6Wl|;SQDs zqzY?zu2PEhcB~S^M0)}PxpHa6$g4WDeJ0^^Cy%^y)SwI7TE57M-jnbJ208RzjEsID z;h|GcwOmkaHCI+us^?356~U^&c%%JILo6YUHGwE)=oUj(CKlUg%F62!9%F-gN}zPr zvd9ca&KdCaK0HA-g0D%monaETu+2uP_JN`oJ#aX&&R(mYETfVrZ zl)X9Oe%e%K`h@le4byaBV7&_6a-1lqK_l3gki#ym4?}(JoKsi*v1tLLy+I}JRW-_D zm|Sr-R@K&Yd2erXZH4XqPQoEx&=^r*&RcY@h3y>Y{l&qYzCfI4nB zW1OBR4wFmoFnNynTMFwU0bqD1t~dD`#_LG<8a>YWsKd$0QjEtbc)8FCr{9E6HRH!x;%{Nu!#|*Wx`-!A z`tej9ax=lWLontBHHwOo@W(_U@JY*5;&pu5%yg|~DztvQR5l$xV8pfJE@xE7O0tgo zKGiUJ4op%@r*H)~$oF`FcEn4ti`SS(dq$~qgQtjZsE$Y3)L#g{bu}`X)ZBY)c7X*# zf!5AAX7^JIM=?)w3yz4}+9^+@m+p2P7e*=iDZU}0XT;MB@eleVw7~?g3NMYI(c@(F zo?nyO=|mf&5AfVEhBkyp73?`x^zAPs>dvLhlnglH&&>Du!NbcAAJEN?G=ozCaCc+i zXur1>f7Fa$3gCTlT0O1tN;CdeOMD&$55GpG^2adbwo~~P>d)v7zX`o*w9s{7IY2bz x6~gTaK@1SXX$Qt)Yeu;+fv(B6kW$Q2pe%Tg%AP>Av7^|8mFWUZAp_-8v=>BH0Dy$tFRRi7LrGn$?!vc z%0tsBWZKU3wKM&uPN(Zg0(o59FOrVV-fOMB_Sxs}e}DfIz#>cq5rLVqev&oqijl3= zofG54cCy9bBWGK#W1FV#D2NFR9%(09*3_(n?8g3)UUCI`E^-9M3ztAuQ#;i|m<7fi z+b8<5ZZTyhjJrs_BwP!kn4ui%nrT=E0=GIOtoT8U?|x)hhWl7xG8M)*3z^*!h~@0E zoDta*}5KYbQ#37JW(S>dW?+Hv_meN*Y0>c=U2v-DV8z}#m5SqB)^ITP-phv+q zft&4lg31yY$EZ|yT}2!Tfq{Zy>Ff35ecjp8_D!9-3wB8}cQnV4>)>b1Jv3?pw?i*F z2@~kEtzy+NEVt-tjw_3MU&SqHPw&Ofl2w-fQz~xD|ICmY7hP9BuClN6-F6PL`}Io2 zaIf$SVt2RaeP+XJv|u2HH;{y7!*F(~oo67=O` z6%Rd`^=esj^JQ9eL*w-j9k(zBUKr%m8@dRJ61x(7- z9fvh~blwqK?!}kLE69-CfPB1j(_~vOJgFzCwYnnyf)UFtm^DC{@<9 zYS32MCYNRA4mpMF{Zzxr3o7)>@uEE6hq12WISL#?OMmVCXS2d{(ISo0#}c>e22~{K ziZ}Ol*D$l&+pGBnY44VbukeDWgMr21y6`P*=eWEDWU!PkaD%YTk&VhF$4eEv_?l@( zEpNZJq$ix!mvMZL9~Jx{aIFJ^3JsfdHNUaDzLaODjoPYpRCn~U%rC3Z<+ifA{%re2 zurE)fdw44M4^su~VQXbiAW^jIPDx)jFB5(x z<*<8ijxXQ+jOc~RXCGIu5JQCT>GV7Fr!!|5O8>u%@uJy6+BCFmIKa*d<*XNq<_WOIc7rSgr{YMibp*( zsgiL48gG+!7gyiJs2_M)Rvh<`^~&H? z5WuZ^Yhc(f>qCipjtA*;d=vsF=^6;5(3yUElEh4d%He<@BCnJH_LS7Jqm#rNI>&q= z^A}=oeU8tUGBdwnF#<^tZD`INDo?S&X*MT=3EcHarhK4)q=$P?y(&f+y@FK|JtIyJ z#6Rd$c$)}0wtjHg&^mvwIsZw^{BQ7Wke_F% zf<+GbE#3PC-~EKH*qi7Z{`A}EyO3)Dk~1b}O13BpF+ha<4v3}Jh)+8szUqK@&>Hc1 LXT&4QjiB-$80zN& diff --git a/bin/main/dev/loki/survivor/SurvivorHealing.class b/bin/main/dev/loki/survivor/SurvivorHealing.class index d4e33e682276217d8ddf5e6b38497930fe80bd2e..07b6c86a87a3f38fa17cd9eccb1da6444751e8cf 100644 GIT binary patch literal 6297 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 aet$p{{%br>8_zQ=Z;(R5ChWq2{Qm*zY@yfy literal 5528 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 literal 0 HcmV?d00001 diff --git a/bin/main/dev/loki/ui/BossBarManager.class b/bin/main/dev/loki/ui/BossBarManager.class new file mode 100644 index 0000000000000000000000000000000000000000..ea4be78da7fc56a6c76a87c8e0bc37427971b26e GIT binary patch literal 7318 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 xa<|+gZ-9~UHgxL)?Lwb-5`Q5AW%{U5Lkg((04 literal 0 HcmV?d00001 diff --git a/bin/main/dev/loki/ui/Sidebar.class b/bin/main/dev/loki/ui/Sidebar.class index 17bd6ed425cc7065b3c94ed37a0f433c37eb9306..7734a963d95feb18f64f782d27676de097939985 100644 GIT binary patch literal 12263 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 DBkF%t literal 8280 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☠ МАНЬЯК РЯДОМ ☠" + + # Цвет: PINK, BLUE, RED, GREEN, YELLOW, PURPLE, WHITE + color: RED + + # Стиль: SOLID, SEGMENTED_6, SEGMENTED_10, SEGMENTED_12, SEGMENTED_20 + style: SOLID + + # Расстояние начала появления (блоков) + appear-range: 32 + + # Расстояние полного заполнения (блоков) + full-range: 8 + # Окна window: # Быстрый волт выжившего (секунд) vault-survivor-fast: 1.0 - + # Медленный волт выжившего (секунд) vault-survivor-slow: 1.5 - + # Волт киллера (секунд) vault-killer: 1.5 - + # Блокировка после 3 волтов подряд (секунд) vault-block-seconds: 15 @@ -122,17 +200,39 @@ window: endgame: # Время открытия ворот (секунд) gate-open-seconds: 20 - - # Время до коллапса (секунд) + + # Время до конца после открытия ворот (секунд) collapse-seconds: 120 - + # Открытие сундуков (секунд) chest-open-seconds: 3 + # Расстояние для взаимодействия с воротами (блоков) + gate-interaction-range: 3.0 + +# Уведомление маньяка о генераторе +killer-notification: + # Время действия компаса (секунд) + compass-duration-seconds: 5 + +# Скорборд +scoreboard: + # Показывать сердца (жизни) + show-hearts: true + + # Символ сердца + heart-symbol: "♥" + + # Цвет ника: HEALTHY, INJURED (красный), HEALING (зелёный), HOOKED (тёмно-красный) + color-healthy: "WHITE" + color-injured: "RED" + color-healing: "GREEN" + color-hooked: "DARK_RED" + # Люк hatch: # Минимум живых для открытия люка min-survivors-for-open: 1 - + # Условие: живых < починенных генераторов require-more-gens-than-survivors: true diff --git a/src/main/java/dev/loki/config/GameConfig.java b/src/main/java/dev/loki/config/GameConfig.java index ee89075..b19d807 100644 --- a/src/main/java/dev/loki/config/GameConfig.java +++ b/src/main/java/dev/loki/config/GameConfig.java @@ -11,42 +11,42 @@ public class GameConfig { } private void setDefaults() { - // Lobby settings - setDefault("lobby.min-players", 5); + // Lobby + setDefault("lobby.min-players", 4); setDefault("lobby.max-players", 5); - setDefault("lobby.countdown-seconds", 60); + setDefault("lobby.countdown-4-players", 45); + setDefault("lobby.countdown-5-players", 15); setDefault("lobby.role-assign-delay", 5); + setDefault("lobby.server-lobby-count", 3); + setDefault("lobby.persist-lobbies", true); - // Generator settings + // Players scale + setDefault("players.survivor-scale", 1.0); + setDefault("players.killer-scale", 1.5); + + // Generators setDefault("generators.total-count", 7); setDefault("generators.required-count", 5); - setDefault("generators.repair-seconds", 80); + setDefault("generators.repair-seconds", 90); setDefault("generators.regression-rate", 0.25); setDefault("generators.kick-regression-rate", 2.5); + setDefault("generators.kick-regression-percent", 20.0); setDefault("generators.kick-duration-seconds", 30); + setDefault("generators.kick-cooldown-seconds", 30); + setDefault("generators.kick-charge-seconds", 3); + setDefault("generators.interaction-range", 3.0); - // Skill check settings (legacy - moved to skillcheck.yml) - setDefault("skillcheck.zone-size", 0.15); - setDefault("skillcheck.rotation-speed", 3.0); - setDefault("skillcheck.success-bonus", 5.0); - setDefault("skillcheck.fail-penalty", 10.0); - - // Colors for UI (&#rrggbb format support) - setDefault("colors.progress-bar-complete", "�FF00"); - setDefault("colors.progress-bar-incomplete", "&#FF0000"); - setDefault("colors.progress-bar-background", "񑘕"); - setDefault("colors.hp-healthy", "�FF00"); - setDefault("colors.hp-injured", "&#FFA500"); - setDefault("colors.hp-downed", "&#FF0000"); - setDefault("colors.hp-dead", "B0000"); - - // Hook settings + // Hooks setDefault("hooks.stage1-seconds", 60); setDefault("hooks.stage2-seconds", 60); setDefault("hooks.struggle-interval", 2); setDefault("hooks.max-hooks", 3); + setDefault("hooks.interaction-range", 3.0); + setDefault("hooks.rescue-seconds", 3); + setDefault("hooks.sabotage-seconds", 10); + setDefault("hooks.sabotage-recovery-seconds", 60); - // Movement speeds (% of normal) + // Movement setDefault("movement.killer", 115); setDefault("movement.killer-carry", 92); setDefault("movement.survivor-walk", 100); @@ -55,30 +55,66 @@ public class GameConfig { setDefault("movement.exhaustion-duration", 3); setDefault("movement.exhaustion-cooldown", 5); - // Healing settings + // Healing + setDefault("healing.other-heal-seconds", 15); setDefault("healing.self-heal-seconds", 30); - setDefault("healing.other-heal-seconds", 16); setDefault("healing.medkit-bonus", 0.5); + setDefault("healing.interrupt-on-move", true); + setDefault("healing.max-heals-per-match", 4); + setDefault("healing.heal-range", 3.0); - // Killer settings + // Carry / escape + setDefault("carry.escape-jumps", 15); + setDefault("carry.escape-speed-seconds", 3); + setDefault("carry.jump-fill-percent", 6.67); + + // Killer setDefault("killer.attack-cooldown", 2.5); setDefault("killer.attack-range", 3.0); - setDefault("killer.pallet-stun-seconds", 2.5); + setDefault("killer.pallet-stun-seconds", 2.0); setDefault("killer.pallet-break-seconds", 3.0); setDefault("killer.terror-radius", 32); + setDefault("killer.pallet-interaction-range", 3.0); + + // BossBar + setDefault("bossbar.enabled", true); + setDefault("bossbar.title", "☠ МАНЬЯК РЯДОМ ☠"); + setDefault("bossbar.color", "RED"); + setDefault("bossbar.style", "SOLID"); + setDefault("bossbar.appear-range", 32); + setDefault("bossbar.full-range", 8); // Window vault setDefault("window.vault-survivor-fast", 1.0); setDefault("window.vault-survivor-slow", 1.5); setDefault("window.vault-killer", 1.5); + setDefault("window.vault-block-seconds", 15); // Endgame setDefault("endgame.gate-open-seconds", 20); setDefault("endgame.collapse-seconds", 120); setDefault("endgame.chest-open-seconds", 3); + setDefault("endgame.gate-interaction-range", 3.0); + + // Killer notification + setDefault("killer-notification.compass-duration-seconds", 5); + + // Scoreboard + setDefault("scoreboard.show-hearts", true); + setDefault("scoreboard.heart-symbol", "♥"); + setDefault("scoreboard.color-healthy", "WHITE"); + setDefault("scoreboard.color-injured", "RED"); + setDefault("scoreboard.color-healing", "GREEN"); + setDefault("scoreboard.color-hooked", "DARK_RED"); + + // Colors for UI + setDefault("colors.progress-bar-complete", "�FF00"); + setDefault("colors.progress-bar-incomplete", "&#FF0000"); + setDefault("colors.progress-bar-background", "񑘕"); // Hatch setDefault("hatch.min-survivors-for-open", 1); + setDefault("hatch.require-more-gens-than-survivors", true); } private void setDefault(String path, Object value) { @@ -87,99 +123,130 @@ public class GameConfig { } } - public int getMinPlayers() { - return config.getInt("lobby.min-players", 5); - } + // ------------------------------------------------------------------------- + // Lobby + // ------------------------------------------------------------------------- + public int getMinPlayers() { return config.getInt("lobby.min-players", 4); } + public int getMaxPlayers() { return config.getInt("lobby.max-players", 5); } + public int getCountdown4Players() { return config.getInt("lobby.countdown-4-players", 45); } + public int getCountdown5Players() { return config.getInt("lobby.countdown-5-players", 15); } + /** @deprecated Use getCountdown4Players() / getCountdown5Players() */ + @Deprecated + public int getLobbyCountdown() { return getCountdown4Players(); } + public int getRoleAssignDelay() { return config.getInt("lobby.role-assign-delay", 5); } + public int getServerLobbyCount() { return config.getInt("lobby.server-lobby-count", 3); } + public boolean isPersistLobbies() { return config.getBoolean("lobby.persist-lobbies", true); } - public int getMaxPlayers() { - return config.getInt("lobby.max-players", 5); - } + // ------------------------------------------------------------------------- + // Players + // ------------------------------------------------------------------------- + public double getSurvivorScale() { return config.getDouble("players.survivor-scale", 1.0); } + public double getKillerScale() { return config.getDouble("players.killer-scale", 1.5); } - public int getLobbyCountdown() { - return config.getInt("lobby.countdown-seconds", 60); - } + // ------------------------------------------------------------------------- + // Generators + // ------------------------------------------------------------------------- + public int getGeneratorTotal() { return config.getInt("generators.total-count", 7); } + public int getGeneratorRequired() { return config.getInt("generators.required-count", 5); } + public int getGeneratorRepairSeconds() { return config.getInt("generators.repair-seconds", 90); } + public double getRegressionRate() { return config.getDouble("generators.regression-rate", 0.25); } + public double getKickRegressionRate() { return config.getDouble("generators.kick-regression-rate", 2.5); } + public double getKickRegressionPercent() { return config.getDouble("generators.kick-regression-percent", 20.0); } + public int getKickDuration() { return config.getInt("generators.kick-duration-seconds", 30); } + public int getKickCooldownSeconds() { return config.getInt("generators.kick-cooldown-seconds", 30); } + public int getKickChargeSeconds() { return config.getInt("generators.kick-charge-seconds", 3); } + public double getGeneratorInteractionRange() { return config.getDouble("generators.interaction-range", 3.0); } - public int getRoleAssignDelay() { - return config.getInt("lobby.role-assign-delay", 5); - } + // ------------------------------------------------------------------------- + // Hooks + // ------------------------------------------------------------------------- + public int getStage1Seconds() { return config.getInt("hooks.stage1-seconds", 60); } + public int getStage2Seconds() { return config.getInt("hooks.stage2-seconds", 60); } + public int getStruggleInterval() { return config.getInt("hooks.struggle-interval", 2); } + public int getMaxHooks() { return config.getInt("hooks.max-hooks", 3); } + public double getHookInteractionRange() { return config.getDouble("hooks.interaction-range", 3.0); } + public int getRescueSeconds() { return config.getInt("hooks.rescue-seconds", 3); } + public int getSabotageSeconds() { return config.getInt("hooks.sabotage-seconds", 10); } + public int getSabotageRecoverySeconds() { return config.getInt("hooks.sabotage-recovery-seconds", 60); } - public int getGeneratorTotal() { - return config.getInt("generators.total-count", 7); - } + // ------------------------------------------------------------------------- + // Movement + // ------------------------------------------------------------------------- + public double getKillerSpeed() { return config.getDouble("movement.killer", 115) / 100.0; } + public double getKillerCarrySpeed() { return config.getDouble("movement.killer-carry", 92) / 100.0; } + public double getSurvivorWalkSpeed() { return config.getDouble("movement.survivor-walk", 100) / 100.0; } + public double getSurvivorSprintSpeed() { return config.getDouble("movement.survivor-sprint", 150) / 100.0; } + public double getSurvivorCrawlSpeed() { return config.getDouble("movement.survivor-crawl", 30) / 100.0; } + public int getExhaustionDuration() { return config.getInt("movement.exhaustion-duration", 3); } + public int getExhaustionCooldown() { return config.getInt("movement.exhaustion-cooldown", 5); } - public int getGeneratorRequired() { - return config.getInt("generators.required-count", 5); - } + // ------------------------------------------------------------------------- + // Healing + // ------------------------------------------------------------------------- + public int getOtherHealSeconds() { return config.getInt("healing.other-heal-seconds", 15); } + public int getSelfHealSeconds() { return config.getInt("healing.self-heal-seconds", 30); } + public double getMedkitBonus() { return config.getDouble("healing.medkit-bonus", 0.5); } + public boolean isHealInterruptOnMove() { return config.getBoolean("healing.interrupt-on-move", true); } + public int getMaxHealsPerMatch() { return config.getInt("healing.max-heals-per-match", 4); } + public double getHealRange() { return config.getDouble("healing.heal-range", 3.0); } - public int getGeneratorRepairSeconds() { - return config.getInt("generators.repair-seconds", 80); - } + // ------------------------------------------------------------------------- + // Carry / Escape + // ------------------------------------------------------------------------- + public int getCarryEscapeJumps() { return config.getInt("carry.escape-jumps", 15); } + public int getCarryEscapeSpeedSeconds() { return config.getInt("carry.escape-speed-seconds", 3); } + public double getCarryJumpFillPercent() { return config.getDouble("carry.jump-fill-percent", 6.67); } - public double getRegressionRate() { - return config.getDouble("generators.regression-rate", 0.25); - } + // ------------------------------------------------------------------------- + // Killer + // ------------------------------------------------------------------------- + public int getAttackCooldown() { return (int)(config.getDouble("killer.attack-cooldown", 2.5) * 20); } + public double getAttackRange() { return config.getDouble("killer.attack-range", 3.0); } + public int getPalletStunTicks() { return (int)(config.getDouble("killer.pallet-stun-seconds", 2.0) * 20); } + public int getPalletBreakTicks() { return (int)(config.getDouble("killer.pallet-break-seconds", 3.0) * 20); } + public int getTerrorRadius() { return config.getInt("killer.terror-radius", 32); } + public double getPalletInteractionRange() { return config.getDouble("killer.pallet-interaction-range", 3.0); } - public double getKickRegressionRate() { - return config.getDouble("generators.kick-regression-rate", 2.5); - } + // ------------------------------------------------------------------------- + // BossBar + // ------------------------------------------------------------------------- + public boolean isBossBarEnabled() { return config.getBoolean("bossbar.enabled", true); } + public String getBossBarTitle() { return config.getString("bossbar.title", "☠ МАНЬЯК РЯДОМ ☠"); } + public String getBossBarColor() { return config.getString("bossbar.color", "RED"); } + public String getBossBarStyle() { return config.getString("bossbar.style", "SOLID"); } + public int getBossBarAppearRange() { return config.getInt("bossbar.appear-range", 32); } + public int getBossBarFullRange() { return config.getInt("bossbar.full-range", 8); } - public int getKickDuration() { - return config.getInt("generators.kick-duration-seconds", 30); - } + // ------------------------------------------------------------------------- + // Window + // ------------------------------------------------------------------------- + public int getVaultSurvivorFastTicks() { return (int)(config.getDouble("window.vault-survivor-fast", 1.0) * 20); } + public int getVaultSurvivorSlowTicks() { return (int)(config.getDouble("window.vault-survivor-slow", 1.5) * 20); } + public int getVaultKillerTicks() { return (int)(config.getDouble("window.vault-killer", 1.5) * 20); } + public int getVaultBlockSeconds() { return config.getInt("window.vault-block-seconds", 15); } - public int getStage1Seconds() { - return config.getInt("hooks.stage1-seconds", 60); - } + // ------------------------------------------------------------------------- + // Endgame + // ------------------------------------------------------------------------- + public int getGateOpenSeconds() { return config.getInt("endgame.gate-open-seconds", 20); } + public int getCollapseSeconds() { return config.getInt("endgame.collapse-seconds", 120); } + public int getChestOpenSeconds() { return config.getInt("endgame.chest-open-seconds", 3); } + public double getGateInteractionRange() { return config.getDouble("endgame.gate-interaction-range", 3.0); } - public int getStage2Seconds() { - return config.getInt("hooks.stage2-seconds", 60); - } + // ------------------------------------------------------------------------- + // Killer notification + // ------------------------------------------------------------------------- + public int getCompassDurationSeconds() { return config.getInt("killer-notification.compass-duration-seconds", 5); } - public int getMaxHooks() { - return config.getInt("hooks.max-hooks", 3); - } + // ------------------------------------------------------------------------- + // Scoreboard + // ------------------------------------------------------------------------- + public boolean isShowHearts() { return config.getBoolean("scoreboard.show-hearts", true); } + public String getHeartSymbol() { return config.getString("scoreboard.heart-symbol", "♥"); } + public String getColorHealthy() { return config.getString("scoreboard.color-healthy", "WHITE"); } + public String getColorInjured() { return config.getString("scoreboard.color-injured", "RED"); } + public String getColorHealing() { return config.getString("scoreboard.color-healing", "GREEN"); } + public String getColorHooked() { return config.getString("scoreboard.color-hooked", "DARK_RED"); } - public double getKillerSpeed() { - return config.getDouble("movement.killer", 115) / 100.0; - } - - public double getKillerCarrySpeed() { - return config.getDouble("movement.killer-carry", 92) / 100.0; - } - - public double getSurvivorWalkSpeed() { - return config.getDouble("movement.survivor-walk", 100) / 100.0; - } - - public double getSurvivorSprintSpeed() { - return config.getDouble("movement.survivor-sprint", 150) / 100.0; - } - - public double getSurvivorCrawlSpeed() { - return config.getDouble("movement.survivor-crawl", 30) / 100.0; - } - - public int getAttackCooldown() { - return (int) (config.getDouble("killer.attack-cooldown", 2.5) * 20); - } - - public double getAttackRange() { - return config.getDouble("killer.attack-range", 3.0); - } - - public int getPalletStunTicks() { - return (int) (config.getDouble("killer.pallet-stun-seconds", 2.5) * 20); - } - - public int getPalletBreakTicks() { - return (int) (config.getDouble("killer.pallet-break-seconds", 3.0) * 20); - } - - public int getTerrorRadius() { - return config.getInt("killer.terror-radius", 32); - } - - public FileConfiguration getConfig() { - return config; - } + public FileConfiguration getConfig() { return config; } } diff --git a/src/main/java/dev/loki/core/GameSession.java b/src/main/java/dev/loki/core/GameSession.java index 53ce2e4..2e14d0b 100644 --- a/src/main/java/dev/loki/core/GameSession.java +++ b/src/main/java/dev/loki/core/GameSession.java @@ -94,7 +94,10 @@ public class GameSession { return new GameplayPhase(generatorManager); } if (currentPhase instanceof GameplayPhase) { - return new EndgamePhase(); + return new EndgamePhase( + plugin.getConfigManager().getGame().getCollapseSeconds(), + gateManager, null, null + ); } return null; } diff --git a/src/main/java/dev/loki/gate/ExitGate.java b/src/main/java/dev/loki/gate/ExitGate.java index 310c0b7..8074653 100644 --- a/src/main/java/dev/loki/gate/ExitGate.java +++ b/src/main/java/dev/loki/gate/ExitGate.java @@ -8,6 +8,7 @@ public class ExitGate { private final Location location; private GateState state; private double progress; + private double savedProgress; // сохраняется при уходе игрока private boolean powered; public enum GateState { @@ -20,6 +21,7 @@ public class ExitGate { this.location = location; this.state = GateState.CLOSED; this.progress = 0.0; + this.savedProgress = 0.0; this.powered = false; } @@ -72,7 +74,8 @@ public class ExitGate { public void updateProgress(double delta) { if (state != GateState.OPENING) return; - progress += delta; + // Линейная установка — delta это абсолютный шаг, не additive accumulation + progress = Math.min(1.0, progress + delta); if (progress >= 1.0) { progress = 1.0; state = GateState.OPEN; @@ -80,9 +83,29 @@ public class ExitGate { } } + /** Прямая установка прогресса (для линейного расчёта в GateOpening). */ + public void setProgressDirect(double value) { + if (state != GateState.OPENING) return; + progress = Math.min(1.0, Math.max(0.0, value)); + if (progress >= 1.0) { + progress = 1.0; + state = GateState.OPEN; + playOpenedSound(); + } + } + + public double getSavedProgress() { return savedProgress; } + + /** Сохранить текущий прогресс (вызывается когда игрок уходит от ворот) */ + public void saveProgress() { savedProgress = progress; } + + /** Восстановить сохранённый прогресс (вызывается когда игрок подходит снова) */ + public void restoreProgress() { progress = savedProgress; } + public void reset() { this.state = GateState.CLOSED; this.progress = 0.0; + this.savedProgress = 0.0; this.powered = false; } diff --git a/src/main/java/dev/loki/gate/GateManager.java b/src/main/java/dev/loki/gate/GateManager.java index 923547e..074604e 100644 --- a/src/main/java/dev/loki/gate/GateManager.java +++ b/src/main/java/dev/loki/gate/GateManager.java @@ -75,9 +75,10 @@ public class GateManager { public void activateEndgame() { endgameActive = true; + // Только разблокировать ворота — игрок должен открывать их вручную for (ExitGate gate : gates) { gate.setPowered(true); - gate.startOpening(); + // НЕ вызываем gate.startOpening() — ворота открываются игроком } } diff --git a/src/main/java/dev/loki/gate/GateOpening.java b/src/main/java/dev/loki/gate/GateOpening.java index 0976f19..08f2ec4 100644 --- a/src/main/java/dev/loki/gate/GateOpening.java +++ b/src/main/java/dev/loki/gate/GateOpening.java @@ -68,6 +68,9 @@ public class GateOpening { Player player = survivor.getPlayer(); if (player == null) return; + // Восстановить сохранённый прогресс + gate.restoreProgress(); + openingPlayers.put(survivor.getUuid(), new OpeningProgress(gate, 0)); player.sendMessage("§aОткрываем ворота... (зажмите ПКМ)"); @@ -115,6 +118,7 @@ public class GateOpening { public void run() { Player player = survivor.getPlayer(); if (player == null || !player.isOnline()) { + gate.saveProgress(); stopOpening(survivor); cancel(); return; @@ -127,6 +131,7 @@ public class GateOpening { } if (!gate.isInRange(player.getLocation(), INTERACTION_RADIUS)) { + gate.saveProgress(); stopOpening(survivor); cancel(); return; @@ -135,10 +140,13 @@ public class GateOpening { progress.ticks++; playOpeningEffect(player, gate, progress.ticks); - double progressPct = progress.ticks / (double) openTicks; - gate.updateProgress(progressPct); + // Линейный прогресс: устанавливаем абсолютное значение от стартовой точки + double progressPct = gate.getSavedProgress() + + (1.0 - gate.getSavedProgress()) * (progress.ticks / (double) openTicks); + // Используем setProgress вместо additive updateProgress + gate.setProgressDirect(progressPct); - if (progress.ticks >= openTicks) { + if (progressPct >= 1.0) { completeOpening(survivor, gate); cancel(); } diff --git a/src/main/java/dev/loki/hook/Hook.java b/src/main/java/dev/loki/hook/Hook.java index f41da59..2460e8d 100644 --- a/src/main/java/dev/loki/hook/Hook.java +++ b/src/main/java/dev/loki/hook/Hook.java @@ -13,6 +13,7 @@ public class Hook { private int stageProgress; private boolean occupied; private boolean basement; + private boolean disabled; // саботирован выжившим public Hook(UUID id, Location location, boolean basement) { this.id = id; @@ -67,4 +68,6 @@ public class Hook { public HookStage getCurrentStage() { return currentStage; } public boolean isOccupied() { return occupied; } public boolean isBasement() { return basement; } + public boolean isDisabled() { return disabled; } + public void setDisabled(boolean disabled) { this.disabled = disabled; } } diff --git a/src/main/java/dev/loki/hook/HookManager.java b/src/main/java/dev/loki/hook/HookManager.java index ba8ae08..f3cccfd 100644 --- a/src/main/java/dev/loki/hook/HookManager.java +++ b/src/main/java/dev/loki/hook/HookManager.java @@ -2,6 +2,7 @@ package dev.loki.hook; import dev.loki.config.LocationsConfig; import dev.loki.mdbd; +import dev.loki.player.PlayerState; import dev.loki.player.Survivor; import org.bukkit.Location; import org.bukkit.scheduler.BukkitTask; @@ -12,12 +13,17 @@ public class HookManager { private final mdbd plugin; private final Map hooks; private BukkitTask tickTask; + private int maxHooks = 3; // default, overridden from config public HookManager(mdbd plugin) { this.plugin = plugin; this.hooks = new HashMap<>(); } + public void setMaxHooks(int max) { + this.maxHooks = max; + } + public void loadHooks(LocationsConfig config) { hooks.clear(); List hookLocations = config.getHooks(); @@ -50,7 +56,7 @@ public class HookManager { public Optional findAvailableHook() { return hooks.values().stream() - .filter(h -> !h.isOccupied()) + .filter(h -> !h.isOccupied() && !h.isDisabled()) .min(Comparator.comparingInt(h -> h.isBasement() ? 1 : 0)); } @@ -58,7 +64,7 @@ public class HookManager { Hook nearest = null; double minDist = maxDistance; for (Hook hook : hooks.values()) { - if (hook.isOccupied()) continue; + if (hook.isOccupied() || hook.isDisabled()) continue; double dist = hook.getLocation().distance(location); if (dist < minDist) { minDist = dist; @@ -83,6 +89,18 @@ public class HookManager { } public boolean tryHookSurvivor(Survivor survivor, Location killerLoc) { + // Проверяем лимит крюков → если превышен, сразу смерть + if (survivor.getHooksTaken() >= maxHooks) { + survivor.setState(PlayerState.DEAD); + var p = survivor.getPlayer(); + if (p != null) { + p.sendMessage(net.kyori.adventure.text.Component.text( + "[DBD] Ваши жизни исчерпаны. Вы погибли.", + net.kyori.adventure.text.format.NamedTextColor.DARK_RED)); + } + return true; // "повешен" = завершён + } + Hook nearest = findNearestUnoccupiedHook(killerLoc, 3.0); if (nearest == null) { nearest = findAvailableHook().orElse(null); diff --git a/src/main/java/dev/loki/hook/HookRescue.java b/src/main/java/dev/loki/hook/HookRescue.java index e332e47..c87425c 100644 --- a/src/main/java/dev/loki/hook/HookRescue.java +++ b/src/main/java/dev/loki/hook/HookRescue.java @@ -1,28 +1,37 @@ package dev.loki.hook; +import dev.loki.config.GameConfig; import dev.loki.mdbd; import dev.loki.player.PlayerState; import dev.loki.player.Survivor; import dev.loki.ui.ActionBar; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Location; import org.bukkit.entity.Player; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; import org.bukkit.scheduler.BukkitTask; import java.util.HashMap; import java.util.Map; import java.util.UUID; +/** + * Hook rescue mechanic: rescuer must hold Shift near the hook for + * config rescue-seconds (default 3) without moving away. + * After success the victim gets Speed I for 5 seconds. + */ public class HookRescue { private final mdbd plugin; private final ActionBar actionBar; - private final Map activeRescues; - private static final int RESCUE_DURATION_TICKS = 40; - private static final double RESCUE_RANGE = 3.0; + private final GameConfig config; + private final Map activeRescues = new HashMap<>(); - public HookRescue(mdbd plugin, ActionBar actionBar) { - this.plugin = plugin; - this.actionBar = actionBar; - this.activeRescues = new HashMap<>(); + public HookRescue(mdbd plugin, ActionBar actionBar, GameConfig config) { + this.plugin = plugin; + this.actionBar = actionBar; + this.config = config; } public boolean startRescue(Survivor rescuer, Hook hook) { @@ -30,22 +39,24 @@ public class HookRescue { if (activeRescues.containsKey(uuid)) return false; if (!hook.isOccupied() || hook.getHookedSurvivor() == null) return false; - Location hookLoc = hook.getLocation(); Player player = rescuer.getPlayer(); - if (player == null || player.getLocation().distance(hookLoc) > RESCUE_RANGE) { - return false; - } + double rescueRange = config.getHookInteractionRange(); + if (player == null || player.getLocation().distance(hook.getLocation()) > rescueRange) return false; - RescueSession session = new RescueSession(rescuer, hook, RESCUE_DURATION_TICKS); + // Rescuer cannot rescue themselves + if (hook.getHookedSurvivor().getUuid().equals(rescuer.getUuid())) return false; + + int durationTicks = config.getRescueSeconds() * 20; + RescueSession session = new RescueSession(rescuer, hook, durationTicks); activeRescues.put(uuid, session); BukkitTask task = plugin.getServer().getScheduler().runTaskTimer( - plugin, - () -> tickRescue(uuid), - 1L, 1L - ); + plugin, () -> tickRescue(uuid), 1L, 1L); session.setTask(task); + if (player.isOnline()) { + player.sendMessage(Component.text("[DBD] Спасаем... держите Shift!", NamedTextColor.YELLOW)); + } return true; } @@ -57,20 +68,21 @@ public class HookRescue { Hook hook = session.getHook(); Player player = rescuer.getPlayer(); - if (player == null || !player.isOnline()) { - cancelRescue(uuid); - return; - } + if (player == null || !player.isOnline()) { cancelRescue(uuid); return; } - if (player.getLocation().distance(hook.getLocation()) > RESCUE_RANGE) { + // Must keep sneaking + if (!player.isSneaking()) { cancelRescue(uuid); return; } + + double rescueRange = config.getHookInteractionRange(); + if (player.getLocation().distance(hook.getLocation()) > rescueRange) { cancelRescue(uuid); - actionBar.sendMessage(player, "Rescue cancelled - too far!"); + player.sendMessage(Component.text("[DBD] Слишком далеко! Спасение прервано.", NamedTextColor.RED)); return; } session.incrementProgress(); double percent = session.getProgressPercent(); - actionBar.showProgress(player, "Rescuing...", percent); + actionBar.showProgress(player, "Спасение...", percent); if (session.isComplete()) { completeRescue(session); @@ -84,17 +96,27 @@ public class HookRescue { Survivor victim = hook.getHookedSurvivor(); hook.unhookSurvivor(); + if (victim != null) { victim.setState(PlayerState.INJURED); Player victimPlayer = victim.getPlayer(); - if (victimPlayer != null) { - victimPlayer.teleport(hook.getLocation().clone().add(1, 0, 0)); + if (victimPlayer != null && victimPlayer.isOnline()) { + // Teleport slightly away from hook + Location freeLoc = hook.getLocation().clone().add(1.0, 0, 0); + victimPlayer.teleport(freeLoc); + + // Speed I for 5 seconds after rescue + victimPlayer.addPotionEffect( + new PotionEffect(PotionEffectType.SPEED, 5 * 20, 0, false, true)); + + victimPlayer.sendMessage(Component.text("[DBD] Вас спасли! Бегите!", NamedTextColor.GREEN)); } } Player rescuerPlayer = rescuer.getPlayer(); - if (rescuerPlayer != null) { - actionBar.sendMessage(rescuerPlayer, "Rescue complete!"); + if (rescuerPlayer != null && rescuerPlayer.isOnline()) { + rescuerPlayer.sendActionBar(Component.text("[DBD] Союзник спасён!", NamedTextColor.GREEN)); + rescuerPlayer.sendMessage(Component.text("[DBD] Спасение завершено!", NamedTextColor.GREEN)); } } @@ -102,41 +124,40 @@ public class HookRescue { RescueSession session = activeRescues.remove(uuid); if (session != null) { session.cancel(); + Survivor rescuer = session.getRescuer(); + Player player = rescuer.getPlayer(); + if (player != null && player.isOnline()) { + player.sendActionBar(Component.empty()); + } } } - public boolean isRescuing(UUID uuid) { - return activeRescues.containsKey(uuid); - } + public boolean isRescuing(UUID uuid) { return activeRescues.containsKey(uuid); } public void clear() { - for (RescueSession session : activeRescues.values()) { - session.cancel(); - } + for (RescueSession session : activeRescues.values()) session.cancel(); activeRescues.clear(); } + // ------------------------------------------------------------------------- private static class RescueSession { private final Survivor rescuer; private final Hook hook; private final int durationTicks; - private int progressTicks; + private int progressTicks = 0; private BukkitTask task; RescueSession(Survivor rescuer, Hook hook, int durationTicks) { - this.rescuer = rescuer; - this.hook = hook; + this.rescuer = rescuer; + this.hook = hook; this.durationTicks = durationTicks; - this.progressTicks = 0; } void incrementProgress() { progressTicks++; } boolean isComplete() { return progressTicks >= durationTicks; } double getProgressPercent() { return (double) progressTicks / durationTicks * 100; } - - void setTask(BukkitTask task) { this.task = task; } - void cancel() { if (task != null) task.cancel(); } - + void setTask(BukkitTask t) { this.task = t; } + void cancel() { if (task != null) { task.cancel(); task = null; } } Survivor getRescuer() { return rescuer; } Hook getHook() { return hook; } } diff --git a/src/main/java/dev/loki/hook/HookSabotage.java b/src/main/java/dev/loki/hook/HookSabotage.java new file mode 100644 index 0000000..092c67d --- /dev/null +++ b/src/main/java/dev/loki/hook/HookSabotage.java @@ -0,0 +1,150 @@ +package dev.loki.hook; + +import dev.loki.config.GameConfig; +import dev.loki.player.Survivor; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Bukkit; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.scheduler.BukkitTask; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * Hook sabotage mechanic: + * - Survivor holds Shift near a hook for sabotage-seconds (10 sec) → hook disabled + * - Disabled hook recovers after sabotage-recovery-seconds (60 sec) + */ +public class HookSabotage { + + private final Plugin plugin; + private final GameConfig config; + private final HookManager hookManager; + + /** survivorUUID → active sabotage task */ + private final Map sabotageCharging = new HashMap<>(); + /** hookUUID → recovery task */ + private final Map recoveryTasks = new HashMap<>(); + + public HookSabotage(Plugin plugin, GameConfig config, HookManager hookManager) { + this.plugin = plugin; + this.config = config; + this.hookManager = hookManager; + } + + // ------------------------------------------------------------------------- + // Public API — called when survivor starts/stops sneaking near a hook + // ------------------------------------------------------------------------- + + /** Call when survivor starts holding Shift near a hook. */ + public boolean startSabotage(Survivor survivor) { + UUID uuid = survivor.getUuid(); + if (sabotageCharging.containsKey(uuid)) return false; + + Player p = survivor.getPlayer(); + if (p == null) return false; + + Hook hook = hookManager.findNearestUnoccupiedHook( + p.getLocation(), config.getHookInteractionRange()); + if (hook == null || hook.isOccupied() || hook.isDisabled()) return false; + + int chargeTicks = config.getSabotageSeconds() * 20; + + BukkitTask task = new BukkitRunnable() { + int elapsed = 0; + + @Override + public void run() { + Player player = survivor.getPlayer(); + if (player == null || !player.isOnline() || !player.isSneaking()) { + stopSabotage(uuid); + cancel(); + return; + } + Hook h = hookManager.findNearestUnoccupiedHook( + player.getLocation(), config.getHookInteractionRange()); + if (h == null || h != hook) { + stopSabotage(uuid); + cancel(); + return; + } + elapsed++; + double pct = (double) elapsed / chargeTicks * 100; + player.sendActionBar(Component.text( + "§e⚙ Саботаж крюка: " + (int) pct + "%")); + + if (elapsed >= chargeTicks) { + executeSabotage(survivor, hook); + cancel(); + } + } + }.runTaskTimer(plugin, 1L, 1L); + + sabotageCharging.put(uuid, task); + if (p.isOnline()) { + p.sendMessage(Component.text("[DBD] Держите Shift для саботажа крюка...", + NamedTextColor.YELLOW)); + } + return true; + } + + /** Call when survivor releases Shift. */ + public void stopSabotage(UUID survivorUuid) { + BukkitTask task = sabotageCharging.remove(survivorUuid); + if (task != null) { + task.cancel(); + Player p = Bukkit.getPlayer(survivorUuid); + if (p != null && p.isOnline()) p.sendActionBar(Component.empty()); + } + } + + public boolean isSabotaging(UUID survivorUuid) { + return sabotageCharging.containsKey(survivorUuid); + } + + public void shutdown() { + sabotageCharging.values().forEach(BukkitTask::cancel); + sabotageCharging.clear(); + recoveryTasks.values().forEach(BukkitTask::cancel); + recoveryTasks.clear(); + } + + // ------------------------------------------------------------------------- + // Private + // ------------------------------------------------------------------------- + + private void executeSabotage(Survivor survivor, Hook hook) { + sabotageCharging.remove(survivor.getUuid()); + + hook.setDisabled(true); + + Player p = survivor.getPlayer(); + if (p != null && p.isOnline()) { + p.sendActionBar(Component.empty()); + p.sendMessage(Component.text("[DBD] Крюк сломан!", NamedTextColor.GREEN)); + p.getWorld().playSound(p.getLocation(), Sound.ENTITY_ITEM_BREAK, 1.0f, 0.8f); + } + + Bukkit.broadcast(Component.text("[DBD] ⚙ Крюк был сломан выжившим!", + NamedTextColor.YELLOW)); + + // Запускаем восстановление + int recoveryTicks = config.getSabotageRecoverySeconds() * 20; + BukkitTask recovery = new BukkitRunnable() { + @Override + public void run() { + hook.setDisabled(false); + recoveryTasks.remove(hook.getId()); + Bukkit.broadcast(Component.text("[DBD] 🔧 Крюк восстановлен.", + NamedTextColor.GRAY)); + } + }.runTaskLater(plugin, recoveryTicks); + + recoveryTasks.put(hook.getId(), recovery); + } +} diff --git a/src/main/java/dev/loki/hook/HookSacrifice.java b/src/main/java/dev/loki/hook/HookSacrifice.java index f50d399..da7dd3c 100644 --- a/src/main/java/dev/loki/hook/HookSacrifice.java +++ b/src/main/java/dev/loki/hook/HookSacrifice.java @@ -30,6 +30,12 @@ public class HookSacrifice { survivor.setState(PlayerState.DEAD); hook.unhookSurvivor(); + // Convert to SPECTATOR mode + if (player != null && player.isOnline()) { + player.setGameMode(org.bukkit.GameMode.SPECTATOR); + player.sendMessage(Component.text("[DBD] Вы были принесены в жертву. Наблюдайте за матчем.", NamedTextColor.DARK_RED)); + } + announceSacrifice(survivor); } diff --git a/src/main/java/dev/loki/hook/HookStruggle.java b/src/main/java/dev/loki/hook/HookStruggle.java index 5ff4fc8..da15960 100644 --- a/src/main/java/dev/loki/hook/HookStruggle.java +++ b/src/main/java/dev/loki/hook/HookStruggle.java @@ -1,5 +1,6 @@ package dev.loki.hook; +import dev.loki.config.GameConfig; import dev.loki.mdbd; import dev.loki.player.PlayerState; import dev.loki.player.Survivor; @@ -15,14 +16,22 @@ public class HookStruggle { private final TitleUI titleUI; private final Map struggleTasks; private final Map struggleProgress; - private static final int STRUGGLE_INTERVAL_TICKS = 40; + private final int struggleIntervalTicks; private static final int STRUGGLE_WINDOW_TICKS = 20; public HookStruggle(mdbd plugin, TitleUI titleUI) { + this(plugin, titleUI, null); + } + + public HookStruggle(mdbd plugin, TitleUI titleUI, GameConfig config) { this.plugin = plugin; this.titleUI = titleUI; this.struggleTasks = new HashMap<>(); this.struggleProgress = new HashMap<>(); + // Читаем из конфига: struggle-interval в секундах → тики + this.struggleIntervalTicks = (config != null) + ? config.getStruggleInterval() * 20 + : 40; // дефолт 2 сек } public void startStruggle(Survivor survivor, Hook hook) { @@ -32,8 +41,8 @@ public class HookStruggle { BukkitTask task = plugin.getServer().getScheduler().runTaskTimer( plugin, () -> triggerSkillCheck(survivor, hook), - STRUGGLE_INTERVAL_TICKS, - STRUGGLE_INTERVAL_TICKS + struggleIntervalTicks, + struggleIntervalTicks ); struggleTasks.put(uuid, task); struggleProgress.put(uuid, 0); diff --git a/src/main/java/dev/loki/killer/GeneratorKick.java b/src/main/java/dev/loki/killer/GeneratorKick.java new file mode 100644 index 0000000..f7cb0f6 --- /dev/null +++ b/src/main/java/dev/loki/killer/GeneratorKick.java @@ -0,0 +1,175 @@ +package dev.loki.killer; + +import dev.loki.config.GameConfig; +import dev.loki.objective.Generator; +import dev.loki.objective.GeneratorManager; +import dev.loki.player.Killer; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Bukkit; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.scheduler.BukkitTask; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * Killer generator kick: Killer holds Shift near a generator for + * kick-charge-seconds (default 3), then the generator loses + * kick-regression-percent (default 20%) progress. + * + * A shared cooldown of kick-cooldown-seconds applies to ALL generators. + */ +public class GeneratorKick { + + private final Plugin plugin; + private final GameConfig config; + private final GeneratorManager generatorManager; + + /** UUID → ongoing charging task */ + private final Map chargingTasks = new HashMap<>(); + + /** Shared cooldown across all generators: remaining ticks */ + private int globalCooldownTicks = 0; + private BukkitTask cooldownTask; + + public GeneratorKick(Plugin plugin, GameConfig config, GeneratorManager generatorManager) { + this.plugin = plugin; + this.config = config; + this.generatorManager = generatorManager; + } + + // ------------------------------------------------------------------------- + // Public API — called when killer starts/stops sneaking near a generator + // ------------------------------------------------------------------------- + + /** Call when killer starts holding Shift near a generator. */ + public boolean startCharging(Killer killer) { + UUID uuid = killer.getUuid(); + if (chargingTasks.containsKey(uuid)) return false; // already charging + if (globalCooldownTicks > 0) { + Player p = killer.getPlayer(); + if (p != null) { + int secsLeft = (int) Math.ceil(globalCooldownTicks / 20.0); + p.sendMessage(Component.text("[DBD] Кулдаун удара: " + secsLeft + " сек", NamedTextColor.RED)); + } + return false; + } + + Player p = killer.getPlayer(); + if (p == null) return false; + + Generator gen = generatorManager.getNearestGenerator(p.getLocation(), + config.getGeneratorInteractionRange()); + if (gen == null || !gen.canBeRepaired()) return false; + + int chargeTicks = config.getKickChargeSeconds() * 20; + + BukkitTask task = new BukkitRunnable() { + int elapsed = 0; + + @Override + public void run() { + Player player = killer.getPlayer(); + if (player == null || !player.isOnline() || !player.isSneaking()) { + stopCharging(killer.getUuid()); + cancel(); + return; + } + + // Still near the same generator? + Generator g = generatorManager.getNearestGenerator(player.getLocation(), + config.getGeneratorInteractionRange()); + if (g == null || g != gen) { + stopCharging(killer.getUuid()); + cancel(); + return; + } + + elapsed++; + double pct = (double) elapsed / chargeTicks * 100; + player.sendActionBar(Component.text("§c⚡ Разрушение: " + (int) pct + "%")); + + if (elapsed >= chargeTicks) { + executeKick(killer, gen); + cancel(); + } + } + }.runTaskTimer(plugin, 1L, 1L); + + chargingTasks.put(uuid, task); + if (p.isOnline()) p.sendMessage(Component.text("[DBD] Держите Shift для разрушения генератора...", NamedTextColor.RED)); + return true; + } + + /** Call when killer releases Shift or moves away. */ + public void stopCharging(UUID killerUuid) { + BukkitTask task = chargingTasks.remove(killerUuid); + if (task != null) { + task.cancel(); + Player p = Bukkit.getPlayer(killerUuid); + if (p != null && p.isOnline()) { + p.sendActionBar(Component.empty()); + } + } + } + + public boolean isCharging(UUID killerUuid) { + return chargingTasks.containsKey(killerUuid); + } + + public void shutdown() { + for (BukkitTask t : chargingTasks.values()) t.cancel(); + chargingTasks.clear(); + if (cooldownTask != null) { cooldownTask.cancel(); cooldownTask = null; } + } + + // ------------------------------------------------------------------------- + // Private + // ------------------------------------------------------------------------- + + private void executeKick(Killer killer, Generator gen) { + chargingTasks.remove(killer.getUuid()); + + // Remove progress + double regPct = config.getKickRegressionPercent(); + double current = gen.getProgress(); + double newProg = Math.max(0, current - regPct); + gen.setProgress(newProg); + gen.setUnderRegression(true, config.getKickDuration() * 20); + + Player p = killer.getPlayer(); + if (p != null && p.isOnline()) { + p.sendActionBar(Component.empty()); + p.sendMessage(Component.text("[DBD] Генератор поврежден! -" + (int) regPct + "% прогресса", NamedTextColor.RED)); + p.getWorld().playSound(p.getLocation(), Sound.ENTITY_GENERIC_EXPLODE, 0.6f, 1.3f); + } + + // Notify all players nearby + Bukkit.broadcast(Component.text("[DBD] ⚡ Маньяк разрушил генератор!", NamedTextColor.DARK_RED)); + + // Start global cooldown + startGlobalCooldown(); + } + + private void startGlobalCooldown() { + globalCooldownTicks = config.getKickCooldownSeconds() * 20; + if (cooldownTask != null) { cooldownTask.cancel(); } + cooldownTask = new BukkitRunnable() { + @Override + public void run() { + if (globalCooldownTicks > 0) { + globalCooldownTicks -= 20; + } else { + globalCooldownTicks = 0; + cancel(); + cooldownTask = null; + } + } + }.runTaskTimer(plugin, 20L, 20L); + } +} diff --git a/src/main/java/dev/loki/killer/KillerCarry.java b/src/main/java/dev/loki/killer/KillerCarry.java index 3b75715..3464f93 100644 --- a/src/main/java/dev/loki/killer/KillerCarry.java +++ b/src/main/java/dev/loki/killer/KillerCarry.java @@ -2,30 +2,45 @@ package dev.loki.killer; import dev.loki.config.GameConfig; import dev.loki.player.Killer; +import dev.loki.player.PlayerState; import dev.loki.player.Survivor; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; import org.bukkit.scheduler.BukkitTask; import java.util.HashMap; import java.util.Map; import java.util.UUID; +/** + * Manages the Killer carrying a downed Survivor. + * + * Escape mechanic: Survivor spams SPACE (jump) to fill the escape bar. + * When the bar reaches 100% the Survivor breaks free, is teleported + * slightly forward and receives Speed I for carry.escape-speed-seconds. + */ public class KillerCarry { private final Plugin plugin; private final GameConfig config; - private final Map activeCarries; + private final Map activeCarries = new HashMap<>(); private BukkitTask carryTask; + // Separate map: killerUUID -> carried survivor UUID (for jump listener) + private final Map killerToSurvivor = new HashMap<>(); + private static class CarryData { final Killer killer; final Survivor survivor; - double wiggleProgress; + double wiggleProgress; // 0..100 CarryData(Killer killer, Survivor survivor) { - this.killer = killer; + this.killer = killer; this.survivor = survivor; this.wiggleProgress = 0; } @@ -34,22 +49,35 @@ public class KillerCarry { public KillerCarry(Plugin plugin, GameConfig config) { this.plugin = plugin; this.config = config; - this.activeCarries = new HashMap<>(); } - public boolean tryPickup(Killer killer, Survivor survivor) { - if (!canPickup(killer, survivor)) { - return false; + // ------------------------------------------------------------------------- + // Called by PlayerJumpListener when a survivor jumps + // ------------------------------------------------------------------------- + public void onSurvivorJump(UUID survivorUuid) { + for (CarryData data : activeCarries.values()) { + if (data.survivor.getUuid().equals(survivorUuid)) { + data.wiggleProgress += config.getCarryJumpFillPercent(); + showWiggleProgress(data); + if (data.wiggleProgress >= 100) { + Bukkit.getScheduler().runTask(plugin, () -> forceDrop(data.killer)); + } + return; + } } + } + + // ------------------------------------------------------------------------- + // Public API + // ------------------------------------------------------------------------- + + public boolean tryPickup(Killer killer, Survivor survivor) { + if (!canPickup(killer, survivor)) return false; Player killerPlayer = killer.getPlayer(); Player survivorPlayer = survivor.getPlayer(); - if (killerPlayer == null || survivorPlayer == null) return false; - - if (!HitValidator.isValidCarryPosition(killerPlayer, survivorPlayer)) { - return false; - } + if (!HitValidator.isValidCarryPosition(killerPlayer, survivorPlayer)) return false; startCarry(killer, survivor); return true; @@ -57,112 +85,25 @@ public class KillerCarry { public boolean canPickup(Killer killer, Survivor survivor) { if (killer.isCarrying()) return false; - if (!survivor.getState().name().equals("DOWNED")) return false; - - Player killerPlayer = killer.getPlayer(); - Player survivorPlayer = survivor.getPlayer(); - - return killerPlayer != null && survivorPlayer != null && survivorPlayer.isOnline(); - } - - private void startCarry(Killer killer, Survivor survivor) { - killer.setCarrying(true); - - Player survivorPlayer = survivor.getPlayer(); - if (survivorPlayer != null) { - survivorPlayer.setInvisible(true); - } - - CarryData data = new CarryData(killer, survivor); - activeCarries.put(killer.getUuid(), data); - - if (carryTask == null) { - startCarryTask(); - } + if (survivor.getState() != PlayerState.DOWNED) return false; + Player kp = killer.getPlayer(); + Player sp = survivor.getPlayer(); + return kp != null && sp != null && sp.isOnline(); } public boolean drop(Killer killer) { CarryData data = activeCarries.remove(killer.getUuid()); + killerToSurvivor.remove(killer.getUuid()); if (data == null) return false; - - stopCarry(data); + stopCarry(data, false); return true; } - private void stopCarry(CarryData data) { - data.killer.setCarrying(false); - - Player survivorPlayer = data.survivor.getPlayer(); - if (survivorPlayer != null) { - survivorPlayer.setInvisible(false); - survivorPlayer.teleport(data.killer.getPlayer().getLocation()); - } - - if (activeCarries.isEmpty() && carryTask != null) { - carryTask.cancel(); - carryTask = null; - } - } - - private void startCarryTask() { - carryTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> { - activeCarries.values().removeIf(data -> { - if (!isValidCarry(data)) { - stopCarry(data); - return true; - } - - updateCarryPosition(data); - updateWiggle(data); - - return false; - }); - }, 1L, 1L); - } - - private boolean isValidCarry(CarryData data) { - Player killerPlayer = data.killer.getPlayer(); - Player survivorPlayer = data.survivor.getPlayer(); - - if (killerPlayer == null || survivorPlayer == null) return false; - if (!killerPlayer.isOnline() || !survivorPlayer.isOnline()) return false; - - return true; - } - - private void updateCarryPosition(CarryData data) { - Player killerPlayer = data.killer.getPlayer(); - Player survivorPlayer = data.survivor.getPlayer(); - - Location carryLoc = killerPlayer.getLocation().clone().add(0, -0.5, 0); - survivorPlayer.teleport(carryLoc); - } - - private void updateWiggle(CarryData data) { - Player survivorPlayer = data.survivor.getPlayer(); - if (survivorPlayer == null) return; - - if (survivorPlayer.isSneaking()) { - data.wiggleProgress += 1.5; - } - - if (data.wiggleProgress >= 100) { - forceDrop(data.killer); - } - } - public void forceDrop(Killer killer) { CarryData data = activeCarries.remove(killer.getUuid()); + killerToSurvivor.remove(killer.getUuid()); if (data == null) return; - - Player survivorPlayer = data.survivor.getPlayer(); - if (survivorPlayer != null) { - Location dropLoc = survivorPlayer.getLocation(); - dropLoc.add(killer.getPlayer().getLocation().getDirection().multiply(1.5)); - survivorPlayer.teleport(dropLoc); - } - - stopCarry(data); + stopCarry(data, true); } public boolean isCarrying(Killer killer) { @@ -180,10 +121,119 @@ public class KillerCarry { } public void shutdown() { - if (carryTask != null) { - carryTask.cancel(); + if (carryTask != null) { carryTask.cancel(); carryTask = null; } + for (CarryData data : new HashMap<>(activeCarries).values()) { + stopCarry(data, false); } - activeCarries.values().forEach(this::stopCarry); activeCarries.clear(); + killerToSurvivor.clear(); + } + + // ------------------------------------------------------------------------- + // Private + // ------------------------------------------------------------------------- + + private void startCarry(Killer killer, Survivor survivor) { + killer.setCarrying(true); + survivor.setState(PlayerState.CARRIED); + + Player survivorPlayer = survivor.getPlayer(); + if (survivorPlayer != null) { + survivorPlayer.setInvisible(true); + survivorPlayer.sendMessage(Component.text( + "[DBD] Нажимайте ПРОБЕЛ чтобы вырваться!", NamedTextColor.YELLOW)); + } + + CarryData data = new CarryData(killer, survivor); + activeCarries.put(killer.getUuid(), data); + killerToSurvivor.put(killer.getUuid(), survivor.getUuid()); + + if (carryTask == null) startCarryTask(); + } + + private void stopCarry(CarryData data, boolean escaped) { + data.killer.setCarrying(false); + + Player killerPlayer = data.killer.getPlayer(); + Player survivorPlayer = data.survivor.getPlayer(); + + if (survivorPlayer != null && survivorPlayer.isOnline()) { + survivorPlayer.setInvisible(false); + + if (escaped) { + // Drop survivor slightly forward from killer + Location dropLoc = survivorPlayer.getLocation(); + if (killerPlayer != null) { + dropLoc = killerPlayer.getLocation().clone() + .add(killerPlayer.getLocation().getDirection().multiply(2.0)); + } + survivorPlayer.teleport(dropLoc); + + // Speed I + int speedTicks = config.getCarryEscapeSpeedSeconds() * 20; + survivorPlayer.addPotionEffect( + new PotionEffect(PotionEffectType.SPEED, speedTicks, 0, false, true)); + + survivorPlayer.sendMessage(Component.text( + "[DBD] Вы вырвались!", NamedTextColor.GREEN)); + + data.survivor.setState(PlayerState.DOWNED); // still downed, just free + } else { + survivorPlayer.teleport(data.killer.getPlayer() != null + ? data.killer.getPlayer().getLocation() + : survivorPlayer.getLocation()); + data.survivor.setState(PlayerState.DOWNED); + } + + // Clear action bar progress + survivorPlayer.sendActionBar(Component.empty()); + } + + if (activeCarries.isEmpty() && carryTask != null) { + carryTask.cancel(); + carryTask = null; + } + } + + private void startCarryTask() { + carryTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> { + if (activeCarries.isEmpty()) return; + for (CarryData data : new HashMap<>(activeCarries).values()) { + if (!isValidCarry(data)) { + activeCarries.remove(data.killer.getUuid()); + killerToSurvivor.remove(data.killer.getUuid()); + stopCarry(data, false); + continue; + } + updateCarryPosition(data); + showWiggleProgress(data); // refresh bar every tick + } + }, 1L, 1L); + } + + private boolean isValidCarry(CarryData data) { + Player kp = data.killer.getPlayer(); + Player sp = data.survivor.getPlayer(); + return kp != null && sp != null && kp.isOnline() && sp.isOnline(); + } + + private void updateCarryPosition(CarryData data) { + Player killerPlayer = data.killer.getPlayer(); + Player survivorPlayer = data.survivor.getPlayer(); + if (killerPlayer == null || survivorPlayer == null) return; + + Location carryLoc = killerPlayer.getLocation().clone().add(0, -0.5, 0); + survivorPlayer.teleport(carryLoc); + } + + private void showWiggleProgress(CarryData data) { + Player survivorPlayer = data.survivor.getPlayer(); + if (survivorPlayer == null) return; + + double pct = Math.min(100, data.wiggleProgress); + int filled = (int) (pct / 10); + int empty = 10 - filled; + String bar = "§e[" + "§a█".repeat(filled) + "§7░".repeat(empty) + "§e]"; + survivorPlayer.sendActionBar(Component.text("§6Побег: " + bar + " " + (int) pct + "%")); } } diff --git a/src/main/java/dev/loki/killer/KillerController.java b/src/main/java/dev/loki/killer/KillerController.java index 10c098e..e83ffff 100644 --- a/src/main/java/dev/loki/killer/KillerController.java +++ b/src/main/java/dev/loki/killer/KillerController.java @@ -32,19 +32,15 @@ public class KillerController { } public void initialize() { - attackListener = new AttackListener(playerManager, damageHandler, - (killer, survivor) -> tryAttack(killer, survivor)); - attackListener.register(plugin); - + // NOTE: AttackListener is registered externally by GameplayPhase to avoid duplication. + // Do NOT register attackListener here. for (Killer killer : playerManager.getKillers()) { movement.startTracking(killer); } } public void shutdown() { - if (attackListener != null) { - attackListener.unregister(); - } + // attackListener is managed externally — do NOT unregister here attack.shutdown(); carry.shutdown(); movement.shutdown(); diff --git a/src/main/java/dev/loki/listener/PlayerInteractListener.java b/src/main/java/dev/loki/listener/PlayerInteractListener.java index 4808fa5..74bec66 100644 --- a/src/main/java/dev/loki/listener/PlayerInteractListener.java +++ b/src/main/java/dev/loki/listener/PlayerInteractListener.java @@ -12,6 +12,7 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.block.Action; import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerToggleSneakEvent; import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.Plugin; @@ -33,6 +34,8 @@ public class PlayerInteractListener implements Listener { private BiConsumer onRescueAttempt; private BiConsumer onChestInteract; private BiConsumer onHatchInteract; + /** Called when killer toggles sneak: (player, isSneaking) */ + private BiConsumer onKillerSneak; private final Map lastClick = new ConcurrentHashMap<>(); private static final long COOLDOWN_MS = 200; @@ -53,6 +56,16 @@ public class PlayerInteractListener implements Listener { public void setOnRescueAttempt(BiConsumer h) { this.onRescueAttempt = h; } public void setOnChestInteract(BiConsumer h) { this.onChestInteract = h; } public void setOnHatchInteract(BiConsumer h) { this.onHatchInteract = h; } + public void setOnKillerSneak(BiConsumer h) { this.onKillerSneak = h; } + + @EventHandler + public void onSneak(PlayerToggleSneakEvent event) { + Player player = event.getPlayer(); + if (!gameManager.isInGame(player.getUniqueId())) return; + if (onKillerSneak != null) { + onKillerSneak.accept(player, event.isSneaking()); + } + } @EventHandler public void onInteract(PlayerInteractEvent event) { diff --git a/src/main/java/dev/loki/listener/PlayerJumpListener.java b/src/main/java/dev/loki/listener/PlayerJumpListener.java new file mode 100644 index 0000000..f824cdb --- /dev/null +++ b/src/main/java/dev/loki/listener/PlayerJumpListener.java @@ -0,0 +1,55 @@ +package dev.loki.listener; + +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; +import org.bukkit.plugin.Plugin; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import java.util.function.Consumer; + +/** + * Tracks jump events (velocity Y-positive when on ground) to support the + * carry-escape "spam space" mechanic. + */ +public class PlayerJumpListener implements Listener { + + private final Plugin plugin; + private Consumer onJump; + private final Set registered = new HashSet<>(); + + public PlayerJumpListener(Plugin plugin) { + this.plugin = plugin; + } + + public void setOnJump(Consumer callback) { + this.onJump = callback; + } + + public void register() { + plugin.getServer().getPluginManager().registerEvents(this, plugin); + } + + public void unregister() { + HandlerList.unregisterAll(this); + onJump = null; + registered.clear(); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPlayerMove(org.bukkit.event.player.PlayerMoveEvent event) { + // Detect jump: player was on ground and now has positive Y velocity + Player player = event.getPlayer(); + if (!player.isOnGround()) return; + // Paper API: check if player jumped (velocity Y > 0 after the tick) + if (player.getVelocity().getY() > 0.1) { + if (onJump != null) { + onJump.accept(player); + } + } + } +} diff --git a/src/main/java/dev/loki/objective/Generator.java b/src/main/java/dev/loki/objective/Generator.java index c37ebad..58d494f 100644 --- a/src/main/java/dev/loki/objective/Generator.java +++ b/src/main/java/dev/loki/objective/Generator.java @@ -118,6 +118,12 @@ public class Generator { return regressionTicks > 0 && !isCompleted && !isBeingRepaired(); } + /** Convenience: set regression state and kick flag together. */ + public void setUnderRegression(boolean kicked, int durationTicks) { + this.wasKicked = kicked; + this.regressionTicks = durationTicks; + } + public boolean wasKicked() { return wasKicked; } diff --git a/src/main/java/dev/loki/objective/GeneratorManager.java b/src/main/java/dev/loki/objective/GeneratorManager.java index be1b5b7..f8ac492 100644 --- a/src/main/java/dev/loki/objective/GeneratorManager.java +++ b/src/main/java/dev/loki/objective/GeneratorManager.java @@ -12,12 +12,10 @@ import java.util.Map; public class GeneratorManager { private final Map generators; private final int requiredCount; - private int completedCount; public GeneratorManager(GeneratorDataManager dataManager, int requiredCount) { this.generators = new HashMap<>(); this.requiredCount = requiredCount; - this.completedCount = 0; loadGenerators(dataManager); } @@ -28,15 +26,11 @@ public class GeneratorManager { } } - public Generator getGenerator(int id) { - return generators.get(id); - } + public Generator getGenerator(int id) { return generators.get(id); } public Generator getGeneratorAt(Location location) { for (Generator gen : generators.values()) { - if (gen.isLocationInZone(location)) { - return gen; - } + if (gen.isLocationInZone(location)) return gen; } return null; } @@ -44,14 +38,10 @@ public class GeneratorManager { public Generator getNearestGenerator(Location location, double maxDistance) { Generator nearest = null; double nearestDist = maxDistance; - for (Generator gen : generators.values()) { if (!gen.getLocation().getWorld().equals(location.getWorld())) continue; double dist = gen.getDistance(location); - if (dist < nearestDist) { - nearestDist = dist; - nearest = gen; - } + if (dist < nearestDist) { nearestDist = dist; nearest = gen; } } return nearest; } @@ -60,47 +50,21 @@ public class GeneratorManager { return getGeneratorAt(player.getLocation()) != null; } - public List getAllGenerators() { - return new ArrayList<>(generators.values()); - } + public List getAllGenerators() { return new ArrayList<>(generators.values()); } public List getIncompleteGenerators() { List incomplete = new ArrayList<>(); for (Generator gen : generators.values()) { - if (!gen.isCompleted()) { - incomplete.add(gen); - } + if (!gen.isCompleted()) incomplete.add(gen); } return incomplete; } - public int getTotalCount() { - return generators.size(); - } - - public int getRequiredCount() { - return requiredCount; - } + public int getTotalCount() { return generators.size(); } + public int getRequiredCount() { return requiredCount; } + /** Returns actual count of completed generators (recalculated live). */ public int getCompletedCount() { - return completedCount; - } - - public int getRemainingRequired() { - return Math.max(0, requiredCount - completedCount); - } - - public void onGeneratorCompleted(Generator generator) { - if (generator.isCompleted() && !wasCompletedBefore(generator)) { - completedCount++; - } - } - - private boolean wasCompletedBefore(Generator generator) { - return completedCount >= countCompleted(); - } - - private int countCompleted() { int count = 0; for (Generator gen : generators.values()) { if (gen.isCompleted()) count++; @@ -108,16 +72,23 @@ public class GeneratorManager { return count; } + public int getRemainingRequired() { + return Math.max(0, requiredCount - getCompletedCount()); + } + + /** Called when a generator transitions to completed. */ + public void onGeneratorCompleted(Generator generator) { + // Nothing special needed — getCompletedCount() queries live state + } + public boolean areAllRequiredCompleted() { - return completedCount >= requiredCount; + return getCompletedCount() >= requiredCount; } public double getAverageProgress() { if (generators.isEmpty()) return 0; double total = 0; - for (Generator gen : generators.values()) { - total += gen.getProgress(); - } + for (Generator gen : generators.values()) total += gen.getProgress(); return total / generators.size(); } diff --git a/src/main/java/dev/loki/objective/GeneratorRegression.java b/src/main/java/dev/loki/objective/GeneratorRegression.java index 7390102..012a28d 100644 --- a/src/main/java/dev/loki/objective/GeneratorRegression.java +++ b/src/main/java/dev/loki/objective/GeneratorRegression.java @@ -5,35 +5,24 @@ import dev.loki.config.GameConfig; public class GeneratorRegression { private final GameConfig config; - private static final double NORMAL_REGRESSION_PER_TICK = 0.25 / 20.0; - private static final double KICK_REGRESSION_PER_TICK = 2.5 / 20.0; - public GeneratorRegression(GameConfig config) { this.config = config; } public void applyRegression(Generator generator) { - if (generator.isCompleted() || generator.isBeingRepaired()) { - return; - } - + if (generator.isCompleted() || generator.isBeingRepaired()) return; double rate = generator.wasKicked() ? getKickRegressionRate() : getNormalRegressionRate(); generator.removeProgress(rate); } public void onGeneratorKicked(Generator generator) { - generator.setKicked(true); - generator.setRegressionTicks(config.getKickDuration() * 20); + generator.setUnderRegression(true, config.getKickDuration() * 20); } public void tick(Generator generator) { - if (!generator.isUnderRegression()) { - return; - } - + if (!generator.isUnderRegression()) return; applyRegression(generator); generator.decrementRegression(); - if (generator.getRegressionTicks() <= 0) { generator.setKicked(false); } @@ -60,9 +49,4 @@ public class GeneratorRegression { && !generator.isBeingRepaired() && generator.getProgress() > 0; } - - public static double calculateRegressionLoss(double progress, int regressionTicks, boolean wasKicked) { - double rate = wasKicked ? KICK_REGRESSION_PER_TICK : NORMAL_REGRESSION_PER_TICK; - return Math.min(progress, rate * regressionTicks); - } } diff --git a/src/main/java/dev/loki/objective/GeneratorRepair.java b/src/main/java/dev/loki/objective/GeneratorRepair.java index ee11d5f..fc67208 100644 --- a/src/main/java/dev/loki/objective/GeneratorRepair.java +++ b/src/main/java/dev/loki/objective/GeneratorRepair.java @@ -6,7 +6,10 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; import org.bukkit.Bukkit; import org.bukkit.Location; +import org.bukkit.Material; import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.CompassMeta; import java.util.HashMap; import java.util.Map; @@ -160,10 +163,39 @@ public class GeneratorRepair { player.sendActionBar(MiniMessage.miniMessage().deserialize("Отлично! +5%")); } else { generator.removeProgress(10.0); - generator.setKicked(true); - generator.setRegressionTicks(600); + generator.setUnderRegression(true, 600); player.sendMessage(MiniMessage.miniMessage().deserialize("Ошибка! Потеря прогресса!")); stopRepair(player); + notifyKillerWithCompass(player); + } + } + + /** Give every online killer a compass pointing to the failed generator for compass-duration-seconds. */ + private void notifyKillerWithCompass(Player failedPlayer) { + int durationTicks = plugin.getConfigManager().getGame().getCompassDurationSeconds() * 20; + + RepairSession rs = sessions.get(failedPlayer.getUniqueId()); + if (rs == null) return; + Location compassTarget = rs.generator.getLocation(); + + for (Player online : Bukkit.getOnlinePlayers()) { + if (online.getUniqueId().equals(failedPlayer.getUniqueId())) continue; + + online.sendMessage(MiniMessage.miniMessage().deserialize( + "[DBD] ⚡ Выживший допустил ошибку! Компас укажет на генератор!")); + + ItemStack compass = new ItemStack(Material.COMPASS); + CompassMeta meta = (CompassMeta) compass.getItemMeta(); + if (meta != null) { + meta.setLodestoneTracked(false); + meta.setLodestone(compassTarget); + compass.setItemMeta(meta); + } + + ItemStack prev = online.getInventory().getItemInOffHand(); + online.getInventory().setItemInOffHand(compass); + plugin.getServer().getScheduler().runTaskLater(plugin, () -> + online.getInventory().setItemInOffHand(prev), durationTicks); } } diff --git a/src/main/java/dev/loki/phase/EndgamePhase.java b/src/main/java/dev/loki/phase/EndgamePhase.java index c9c3e50..ac16dd9 100644 --- a/src/main/java/dev/loki/phase/EndgamePhase.java +++ b/src/main/java/dev/loki/phase/EndgamePhase.java @@ -2,7 +2,6 @@ package dev.loki.phase; import dev.loki.core.GameSession; import dev.loki.gate.GateManager; -import dev.loki.gate.GateOpening; import dev.loki.hatch.Hatch; import dev.loki.hatch.HatchCondition; import dev.loki.hatch.HatchEscape; @@ -10,28 +9,35 @@ import dev.loki.player.BasePlayer; import dev.loki.player.Killer; import dev.loki.player.PlayerState; import dev.loki.player.Survivor; +import dev.loki.ui.Sidebar; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.title.Title; import org.bukkit.Sound; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; +import java.time.Duration; import java.util.ArrayList; import java.util.List; public class EndgamePhase implements Phase { - private final int collapseSeconds; + private int collapseSeconds; private final int openTicks; private int remainingTicks; private boolean complete; private boolean gatesActivated; private GateManager gateManager; - private GateOpening gateOpening; private Hatch hatch; private HatchCondition hatchCondition; private HatchEscape hatchEscape; + private Sidebar sidebar; + /** Default constructor — reads collapseSeconds from config on enter() */ public EndgamePhase() { - this.collapseSeconds = 120; + this.collapseSeconds = -1; // resolved in enter() this.openTicks = 20 * 20; } @@ -45,68 +51,52 @@ public class EndgamePhase implements Phase { this.hatchEscape = new HatchEscape(hatch); } + // ------------------------------------------------------------------------- + // Phase lifecycle + // ------------------------------------------------------------------------- + @Override public void enter(GameSession session) { + // Read collapseSeconds from config if not explicitly set + if (collapseSeconds < 0) { + collapseSeconds = session.getConfig().getCollapseSeconds(); + } this.remainingTicks = collapseSeconds * 20; this.complete = false; this.gatesActivated = false; + // Pick up GateManager from session if not injected via constructor + if (gateManager == null) { + gateManager = session.getGateManager(); + } + + // Set up sidebar with endgame timer + sidebar = new Sidebar(); + for (BasePlayer bp : session.getPlayers().values()) { + Player p = bp.getPlayer(); + if (p != null && p.isOnline()) sidebar.setupPlayer(p); + } + activateEndgame(session); announceEndgameStart(session); } - private void activateEndgame(GameSession session) { - if (gateManager != null && !gatesActivated) { - gateManager.activateEndgame(); - gatesActivated = true; - } - - checkHatchConditions(session); - - session.getPlugin().getLogger().info("Endgame started!"); - } - - private void checkHatchConditions(GameSession session) { - if (hatch == null || hatchCondition == null) return; - - int repairedGens = countRepairedGenerators(session); - - hatchCondition.checkAndReveal(hatch, session.getPlayers().values(), repairedGens); - - if (isLastSurvivor(session)) { - hatchCondition.checkAndOpen(hatch, session.getPlayers().values(), repairedGens); - if (hatch.isOpen()) { - hatchEscape.notifyNearbyPlayers(); - } - } - } - - private boolean isLastSurvivor(GameSession session) { - if (hatchCondition == null) return false; - return hatchCondition.isLastSurvivor(session.getPlayers().values()); - } - - private int countRepairedGenerators(GameSession session) { - return 5; - } - - private void announceEndgameStart(GameSession session) { - for (BasePlayer player : session.getPlayers().values()) { - Player bukkitPlayer = player.getPlayer(); - if (bukkitPlayer != null) { - bukkitPlayer.sendTitle("§4§lЭНДГЕЙМ!", - "§cКоллапс через 2 минуты!", 10, 70, 20); - bukkitPlayer.playSound(bukkitPlayer.getLocation(), - Sound.ENTITY_WITHER_SPAWN, 1.0f, 0.8f); - } - } - } - @Override public void tick(GameSession session) { remainingTicks--; int secondsLeft = remainingTicks / 20; + // Update sidebar every second + if (remainingTicks % 20 == 0 && sidebar != null) { + sidebar.setEndgameTimer(secondsLeft); + for (BasePlayer bp : session.getPlayers().values()) { + Player p = bp.getPlayer(); + if (p != null && p.isOnline()) { + sidebar.updateGameStats(p, session, session.getGeneratorManager()); + } + } + } + updateGates(session); updateHatch(session); checkWinConditions(session); @@ -118,79 +108,12 @@ public class EndgamePhase implements Phase { } } - private void updateGates(GameSession session) { - if (gateManager == null) return; - gateManager.updateAllGates(1.0 / openTicks); - } - - private void updateHatch(GameSession session) { - if (hatch == null || !hatch.isOpen()) return; - - for (BasePlayer player : session.getPlayers().values()) { - if (player.isKiller()) continue; - if (player.getState() != PlayerState.HEALTHY && - player.getState() != PlayerState.INJURED) continue; - - if (hatchEscape.canEscape((Survivor) player)) { - hatchEscape.tryEscape((Survivor) player); - } - } - } - - private void checkWinConditions(GameSession session) { - boolean allEscapedOrDead = true; - int survivorsAlive = 0; - - for (BasePlayer player : session.getPlayers().values()) { - if (player.isKiller()) continue; - - PlayerState state = player.getState(); - if (state != PlayerState.DEAD && state != PlayerState.ESCAPED) { - allEscapedOrDead = false; - survivorsAlive++; - } - } - - if (allEscapedOrDead || survivorsAlive == 0) { - complete = true; - } - } - - private void sendCountdown(GameSession session, int secondsLeft) { - if (secondsLeft > 0 && secondsLeft <= 10) { - for (BasePlayer player : session.getPlayers().values()) { - Player bukkitPlayer = player.getPlayer(); - if (bukkitPlayer != null) { - bukkitPlayer.sendTitle("§4" + secondsLeft, "", 0, 25, 0); - bukkitPlayer.playSound(bukkitPlayer.getLocation(), - Sound.BLOCK_NOTE_BLOCK_HAT, 0.5f, 1.0f); - } - } - } - } - - private void triggerCollapse(GameSession session) { - for (BasePlayer player : session.getPlayers().values()) { - if (player.getState() == PlayerState.DEAD || - player.getState() == PlayerState.ESCAPED) continue; - - Player bukkitPlayer = player.getPlayer(); - if (bukkitPlayer == null) continue; - - if (!player.isKiller()) { - bukkitPlayer.setHealth(0); - player.setState(PlayerState.DEAD); - } else { - ((Killer) player).setCarrying(false); - } - - bukkitPlayer.sendTitle("§4§lКОЛЛАПС!", - "§cКарта разрушена!", 10, 70, 20); - } - } - @Override public void exit(GameSession session) { + if (sidebar != null) { + sidebar.clear(); + sidebar = null; + } announceEndgameEnd(session); if (hatch != null) { @@ -205,42 +128,6 @@ public class EndgamePhase implements Phase { }.runTaskLater(session.getPlugin(), 100L); } - private void announceEndgameEnd(GameSession session) { - List results = new ArrayList<>(); - results.add("§6§l=== РЕЗУЛЬТАТЫ ==="); - - int escaped = 0; - int died = 0; - - for (BasePlayer player : session.getPlayers().values()) { - if (player.isKiller()) continue; - - PlayerState state = player.getState(); - Player bukkitPlayer = player.getPlayer(); - - String name = bukkitPlayer != null ? bukkitPlayer.getName() : "Unknown"; - - if (state == PlayerState.ESCAPED) { - results.add("§a✔ " + name + " — Сбежал"); - escaped++; - } else { - results.add("§c✘ " + name + " — Погиб"); - died++; - } - } - - results.add("§7Выживших: §a" + escaped + " §7/ Погибших: §c" + died); - - String resultMessage = String.join("\n", results); - - for (BasePlayer player : session.getPlayers().values()) { - Player bukkitPlayer = player.getPlayer(); - if (bukkitPlayer != null) { - bukkitPlayer.sendMessage(resultMessage); - } - } - } - @Override public boolean isComplete() { return complete; @@ -249,4 +136,141 @@ public class EndgamePhase implements Phase { public int getRemainingSeconds() { return remainingTicks / 20; } + + // ------------------------------------------------------------------------- + // Private helpers + // ------------------------------------------------------------------------- + + private void activateEndgame(GameSession session) { + if (gateManager != null && !gatesActivated) { + gateManager.activateEndgame(); + gatesActivated = true; + } + checkHatchConditions(session); + session.getPlugin().getLogger().info("Endgame started!"); + } + + private void checkHatchConditions(GameSession session) { + if (hatch == null || hatchCondition == null) return; + int repairedGens = countRepairedGenerators(session); + hatchCondition.checkAndReveal(hatch, session.getPlayers().values(), repairedGens); + if (hatchCondition.isLastSurvivor(session.getPlayers().values())) { + hatchCondition.checkAndOpen(hatch, session.getPlayers().values(), repairedGens); + if (hatch.isOpen() && hatchEscape != null) { + hatchEscape.notifyNearbyPlayers(); + } + } + } + + private int countRepairedGenerators(GameSession session) { + if (session.getGeneratorManager() != null) { + return session.getGeneratorManager().getCompletedCount(); + } + return 0; + } + + private void announceEndgameStart(GameSession session) { + for (BasePlayer bp : session.getPlayers().values()) { + Player p = bp.getPlayer(); + if (p == null) continue; + Title title = Title.title( + Component.text("ЭНДГЕЙМ!").color(TextColor.color(0xCC0000)), + Component.text("Коллапс через " + collapseSeconds + " сек!").color(NamedTextColor.RED), + Title.Times.times(Duration.ofMillis(500), Duration.ofSeconds(3), Duration.ofMillis(500)) + ); + p.showTitle(title); + p.playSound(p.getLocation(), Sound.ENTITY_WITHER_SPAWN, 1.0f, 0.8f); + } + } + + private void updateGates(GameSession session) { + if (gateManager == null) return; + gateManager.updateAllGates(1.0 / openTicks); + } + + private void updateHatch(GameSession session) { + if (hatch == null || !hatch.isOpen() || hatchEscape == null) return; + for (BasePlayer player : session.getPlayers().values()) { + if (player.isKiller()) continue; + PlayerState st = player.getState(); + if (st != PlayerState.HEALTHY && st != PlayerState.INJURED) continue; + if (hatchEscape.canEscape((Survivor) player)) { + hatchEscape.tryEscape((Survivor) player); + } + } + } + + private void checkWinConditions(GameSession session) { + int survivorsAlive = 0; + for (BasePlayer player : session.getPlayers().values()) { + if (player.isKiller()) continue; + PlayerState st = player.getState(); + if (st != PlayerState.DEAD && st != PlayerState.ESCAPED) { + survivorsAlive++; + } + } + if (survivorsAlive == 0) { + complete = true; + } + } + + private void sendCountdown(GameSession session, int secondsLeft) { + if (secondsLeft > 0 && secondsLeft <= 10) { + for (BasePlayer player : session.getPlayers().values()) { + Player p = player.getPlayer(); + if (p == null) continue; + Title t = Title.title( + Component.text(String.valueOf(secondsLeft)).color(TextColor.color(0xFF2222)), + Component.empty(), + Title.Times.times(Duration.ZERO, Duration.ofSeconds(1), Duration.ofMillis(200)) + ); + p.showTitle(t); + p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_HAT, 0.5f, 1.0f); + } + } + } + + private void triggerCollapse(GameSession session) { + for (BasePlayer player : session.getPlayers().values()) { + if (player.getState() == PlayerState.DEAD || player.getState() == PlayerState.ESCAPED) continue; + Player p = player.getPlayer(); + if (p == null) continue; + if (!player.isKiller()) { + p.setHealth(0); + player.setState(PlayerState.DEAD); + } else { + ((Killer) player).setCarrying(false); + } + Title t = Title.title( + Component.text("КОЛЛАПС!").color(TextColor.color(0xFF0000)), + Component.text("Карта разрушена!").color(NamedTextColor.RED), + Title.Times.times(Duration.ofMillis(500), Duration.ofSeconds(3), Duration.ofMillis(500)) + ); + p.showTitle(t); + } + } + + private void announceEndgameEnd(GameSession session) { + List results = new ArrayList<>(); + results.add("§6§l=== РЕЗУЛЬТАТЫ ==="); + int escaped = 0, died = 0; + for (BasePlayer player : session.getPlayers().values()) { + if (player.isKiller()) continue; + Player p = player.getPlayer(); + String name = p != null ? p.getName() : "Unknown"; + if (player.getState() == PlayerState.ESCAPED) { + results.add("§a✔ " + name + " — Сбежал"); + escaped++; + } else { + results.add("§c✘ " + name + " — Погиб"); + died++; + } + } + results.add("§7Выживших: §a" + escaped + " §7/ Погибших: §c" + died); + String msg = String.join("\n", results); + for (BasePlayer player : session.getPlayers().values()) { + Player p = player.getPlayer(); + if (p != null) p.sendMessage(msg); + } + } } diff --git a/src/main/java/dev/loki/phase/GameplayPhase.java b/src/main/java/dev/loki/phase/GameplayPhase.java index 835a8db..e20fb5e 100644 --- a/src/main/java/dev/loki/phase/GameplayPhase.java +++ b/src/main/java/dev/loki/phase/GameplayPhase.java @@ -2,6 +2,7 @@ package dev.loki.phase; import dev.loki.chest.ChestManager; import dev.loki.chest.ChestOpening; +import dev.loki.config.GameConfig; import dev.loki.core.GameSession; import dev.loki.damage.AttackListener; import dev.loki.damage.DamageHandler; @@ -10,10 +11,13 @@ import dev.loki.gate.GateManager; import dev.loki.hook.Hook; import dev.loki.hook.HookManager; import dev.loki.hook.HookRescue; +import dev.loki.hook.HookSabotage; import dev.loki.hook.HookSacrifice; import dev.loki.hook.HookStage; +import dev.loki.killer.GeneratorKick; import dev.loki.killer.KillerController; import dev.loki.listener.PlayerInteractListener; +import dev.loki.listener.PlayerJumpListener; import dev.loki.listener.PlayerMoveListener; import dev.loki.mdbd; import dev.loki.objective.GeneratorManager; @@ -29,9 +33,14 @@ import dev.loki.player.PlayerState; import dev.loki.player.Survivor; import dev.loki.state.StateMachine; import dev.loki.ui.ActionBar; +import dev.loki.ui.BossBarManager; +import dev.loki.ui.Sidebar; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.title.Title; import org.bukkit.Bukkit; +import org.bukkit.GameMode; import org.bukkit.Sound; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -40,6 +49,10 @@ import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.event.entity.EntityDamageEvent; +import java.time.Duration; +import java.util.Collection; +import java.util.stream.Collectors; + public class GameplayPhase implements Phase { private final GeneratorManager generatorManager; @@ -55,9 +68,14 @@ public class GameplayPhase implements Phase { private HookSacrifice hookSacrifice; private PalletDrop palletDrop; private PalletBreak palletBreak; + @SuppressWarnings("unused") private PalletStun palletStun; private ChestManager chestManager; private ChestOpening chestOpening; + private GeneratorKick generatorKick; + private HookSabotage hookSabotage; + private BossBarManager bossBarManager; + private Sidebar sidebar; // Damage system private PlayerManager playerManager; @@ -68,9 +86,10 @@ public class GameplayPhase implements Phase { private Listener vanillaDamageBlocker; private dev.loki.item.ItemUseListener itemUseListener; - // Listener references grabbed from plugin + // Listeners private PlayerMoveListener moveListener; private PlayerInteractListener interactListener; + private PlayerJumpListener jumpListener; public GameplayPhase(GeneratorManager generatorManager) { this.generatorManager = generatorManager; @@ -83,113 +102,141 @@ public class GameplayPhase implements Phase { @Override public void enter(GameSession session) { mdbd plugin = session.getPlugin(); + GameConfig cfg = session.getConfig(); tickCounter = 0; shouldEnd = false; allGensDone = false; - // --- Initialise managers --- + // --- Managers --- palletManager = new PalletManager(plugin.getConfigManager().getLocations()); hookManager = new HookManager(plugin); hookManager.loadHooks(plugin.getConfigManager().getLocations()); + hookManager.setMaxHooks(cfg.getMaxHooks()); gateManager = new GateManager(plugin.getConfigManager().getLocations()); ActionBar ab = new ActionBar(); - hookRescue = new HookRescue(plugin, ab); + hookRescue = new HookRescue(plugin, ab, cfg); hookSacrifice = new HookSacrifice(plugin, null); palletDrop = new PalletDrop(palletManager); - palletBreak = new PalletBreak(session.getConfig()); - palletStun = new PalletStun(session.getConfig()); + palletBreak = new PalletBreak(cfg); + palletStun = new PalletStun(cfg); chestManager = new ChestManager(plugin, plugin.getConfigManager().getLocations()); chestOpening = new ChestOpening(plugin); - // Store managers on session so other code can reach them + // GeneratorKick for the killer + generatorKick = new GeneratorKick(plugin, cfg, generatorManager); + // HookSabotage for survivors + hookSabotage = new HookSabotage(plugin, cfg, hookManager); + session.setPalletManager(palletManager); session.setHookManager(hookManager); session.setGateManager(gateManager); // --- Damage system --- - // Build PlayerManager from current session players playerManager = new PlayerManager(); for (BasePlayer bp : session.getPlayers().values()) { playerManager.register(bp); } - - stateMachine = new StateMachine(); + stateMachine = new StateMachine(); damageHandler = new DamageHandler(stateMachine); - // When survivor is injured — show title + sound damageHandler.setOnSurvivorInjured(survivor -> { Player p = survivor.getPlayer(); if (p != null && p.isOnline()) { - p.showTitle(net.kyori.adventure.title.Title.title( - Component.text("РАНЕН!", net.kyori.adventure.text.format.TextColor.color(0xFF8C00)), - Component.text("Найдите аптечку или союзника", NamedTextColor.YELLOW) + p.showTitle(Title.title( + Component.text("РАНЕН!").color(TextColor.color(0xFF8C00)), + Component.text("Найдите аптечку или союзника").color(NamedTextColor.YELLOW) )); p.playSound(p.getLocation(), Sound.ENTITY_PLAYER_HURT, 1.0f, 0.8f); - // Keep HP at 20 always — damage is state-based not HP-based p.setHealth(20.0); } }); - // When survivor is downed — show title + sound damageHandler.setOnSurvivorDowned(survivor -> { Player p = survivor.getPlayer(); if (p != null && p.isOnline()) { - p.showTitle(net.kyori.adventure.title.Title.title( - Component.text("НОКДАУН!", net.kyori.adventure.text.format.TextColor.color(0xFF2222)), - Component.text("Ждите помощи...", NamedTextColor.RED) + p.showTitle(Title.title( + Component.text("НОКДАУН!").color(TextColor.color(0xFF2222)), + Component.text("Ждите помощи...").color(NamedTextColor.RED) )); p.playSound(p.getLocation(), Sound.ENTITY_PLAYER_HURT, 1.0f, 0.5f); p.setHealth(20.0); } }); - killerController = new KillerController(plugin, playerManager, damageHandler, session.getConfig()); - - // AttackListener — intercepts EntityDamageByEntityEvent, cancels vanilla damage, applies custom + // KillerController — AttackListener зарегистрирован ТОЛЬКО здесь + killerController = new KillerController(plugin, playerManager, damageHandler, cfg); attackListener = new AttackListener(playerManager, damageHandler, (killer, survivor) -> killerController.tryAttack(killer, survivor)); attackListener.register(plugin); - // Block ALL vanilla damage for players in this session (fall, fire, mobs, etc.) + // Block vanilla damage vanillaDamageBlocker = new VanillaDamageBlocker(session); Bukkit.getPluginManager().registerEvents(vanillaDamageBlocker, plugin); + // KillerController.initialize() does NOT re-register AttackListener killerController.initialize(); - // --- Give starting items to survivors & register item use --- + // --- BossBar --- + Collection survivors = session.getPlayers().values().stream() + .filter(bp -> !bp.isKiller()) + .map(bp -> (Survivor) bp) + .collect(Collectors.toList()); + Killer killer = (Killer) session.getPlayers().values().stream() + .filter(BasePlayer::isKiller) + .findFirst().orElse(null); + bossBarManager = new BossBarManager(plugin, cfg); + if (killer != null) bossBarManager.setTargets(killer, survivors); + if (cfg.isBossBarEnabled()) bossBarManager.start(); + + // --- Sidebar --- + sidebar = new Sidebar(); + for (BasePlayer bp : session.getPlayers().values()) { + Player p = bp.getPlayer(); + if (p != null && p.isOnline()) sidebar.setupPlayer(p); + } + + // --- Jump listener for carry escape --- + jumpListener = new PlayerJumpListener(plugin); + jumpListener.setOnJump(player -> { + BasePlayer bp = session.getPlayer(player.getUniqueId()); + if (bp == null || bp.isKiller()) return; + killerController.getCarry().onSurvivorJump(player.getUniqueId()); + }); + jumpListener.register(); + + // --- Items --- itemUseListener = new dev.loki.item.ItemUseListener(plugin, session); itemUseListener.register(); - giveStartingItems(session); + // Apply player scales via Attribute API + applyPlayerScales(session, cfg); + // --- Wire listeners --- moveListener = plugin.getMoveListener(); interactListener = plugin.getInteractListener(); - wireMoveListener(session); wireInteractListener(session); Bukkit.broadcast(Component.text("[DBD] Игра началась! Выжившие, чините генераторы!", - NamedTextColor.GREEN)); + NamedTextColor.GREEN)); } @Override public void tick(GameSession session) { tickCounter++; generatorManager.tick(); - - // Tick hook timers every game tick tickHooks(session); if (tickCounter % 20 == 0) { checkGeneratorProgress(session); checkWinConditions(session); + updateSidebar(session); } } @Override public void exit(GameSession session) { - // Clean up listeners if (moveListener != null) { moveListener.setOnSurvivorMove(null); } @@ -198,41 +245,34 @@ public class GameplayPhase implements Phase { interactListener.setOnRescueAttempt(null); interactListener.setOnChestInteract(null); interactListener.setOnAttack(null); + interactListener.setOnKillerSneak(null); } - - // Stop hooks - if (hookManager != null) { - hookManager.clear(); - } - if (hookRescue != null) { - hookRescue.clear(); - } - if (chestOpening != null) { - chestOpening.cancelAll(); - } - - // Unregister damage listeners - if (attackListener != null) { - attackListener.unregister(); + if (jumpListener != null) { + jumpListener.unregister(); + jumpListener = null; } + if (hookManager != null) hookManager.clear(); + if (hookRescue != null) hookRescue.clear(); + if (chestOpening != null) chestOpening.cancelAll(); + if (attackListener != null) attackListener.unregister(); if (vanillaDamageBlocker != null) { HandlerList.unregisterAll(vanillaDamageBlocker); vanillaDamageBlocker = null; } - if (killerController != null) { - killerController.shutdown(); - } - if (itemUseListener != null) { - itemUseListener.unregister(); - } - if (playerManager != null) { - playerManager.clear(); - } - // Restore HP for all players + if (killerController != null) killerController.shutdown(); + if (generatorKick != null) generatorKick.shutdown(); + if (hookSabotage != null) hookSabotage.shutdown(); + if (bossBarManager != null) bossBarManager.stop(); + if (sidebar != null) { sidebar.clear(); sidebar = null; } + if (itemUseListener != null) itemUseListener.unregister(); + if (playerManager != null) playerManager.clear(); + + // Restore modes and HP for (BasePlayer bp : session.getPlayers().values()) { Player p = bp.getPlayer(); if (p != null && p.isOnline()) { p.setHealth(20.0); + p.setGameMode(GameMode.ADVENTURE); } } @@ -244,6 +284,49 @@ public class GameplayPhase implements Phase { return shouldEnd; } + // ------------------------------------------------------------------------- + // Attribute-based player scaling + // ------------------------------------------------------------------------- + + private void applyPlayerScales(GameSession session, GameConfig cfg) { + for (BasePlayer bp : session.getPlayers().values()) { + Player p = bp.getPlayer(); + if (p == null || !p.isOnline()) continue; + double scale = bp.isKiller() ? cfg.getKillerScale() : cfg.getSurvivorScale(); + try { + var attr = p.getAttribute(org.bukkit.attribute.Attribute.GENERIC_SCALE); + if (attr != null) attr.setBaseValue(scale); + } catch (Exception ignored) { + // Attribute may not exist on older Paper builds — skip silently + } + } + } + + private void resetPlayerScales(GameSession session) { + for (BasePlayer bp : session.getPlayers().values()) { + Player p = bp.getPlayer(); + if (p == null || !p.isOnline()) continue; + try { + var attr = p.getAttribute(org.bukkit.attribute.Attribute.GENERIC_SCALE); + if (attr != null) attr.setBaseValue(1.0); + } catch (Exception ignored) {} + } + } + + // ------------------------------------------------------------------------- + // Sidebar update + // ------------------------------------------------------------------------- + + private void updateSidebar(GameSession session) { + if (sidebar == null) return; + for (BasePlayer bp : session.getPlayers().values()) { + Player p = bp.getPlayer(); + if (p != null && p.isOnline()) { + sidebar.updateGameStats(p, session, generatorManager); + } + } + } + // ------------------------------------------------------------------------- // Starting items // ------------------------------------------------------------------------- @@ -254,7 +337,6 @@ public class GameplayPhase implements Phase { if (bp.isKiller()) continue; Player p = bp.getPlayer(); if (p == null || !p.isOnline()) continue; - // Clear hotbar slots 0-3, give a random item in slot 0 p.getInventory().clear(); p.getInventory().setItem(0, loot.rollLoot()); p.sendMessage(Component.text("[DBD] Вы получили стартовый предмет!", NamedTextColor.YELLOW)); @@ -268,26 +350,45 @@ public class GameplayPhase implements Phase { private void tickHooks(GameSession session) { for (Hook hook : hookManager.getAllHooks()) { if (!hook.isOccupied()) continue; - hook.tick(); Survivor victim = hook.getHookedSurvivor(); if (victim == null) continue; - // Show action bar progress to the hooked survivor Player vp = victim.getPlayer(); if (vp != null && vp.isOnline()) { - // ActionBar shows stage progress double pct = hook.getStageProgressPercent(); String label = hook.getCurrentStage() == HookStage.STAGE_2 - ? "§cStage 2 - struggle!" - : "§eOn hook"; + ? "§cStage 2 — вырывайтесь!" : "§eНа крюке"; vp.sendActionBar(Component.text(label + " " + (int) pct + "%")); } - // Sacrifice when timer expires if (hook.getCurrentStage() == HookStage.SACRIFICE) { hookSacrifice.sacrificeIfExpired(victim, hook); + // Перевод в SPECTATOR после смерти обрабатывается через setState + } + } + } + + // ------------------------------------------------------------------------- + // Spectator mode on death / after sacrifice + // ------------------------------------------------------------------------- + + /** + * Converts a dead survivor to SPECTATOR so they can watch the game. + */ + private void onSurvivorDied(Survivor survivor, GameSession session) { + Player p = survivor.getPlayer(); + if (p == null || !p.isOnline()) return; + p.setGameMode(GameMode.SPECTATOR); + p.sendMessage(Component.text("[DBD] Вы мертвы. Наблюдайте за матчем.", NamedTextColor.DARK_RED)); + // Let spectator follow a random alive player + for (BasePlayer other : session.getPlayers().values()) { + if (other.getUuid().equals(survivor.getUuid())) continue; + Player op = other.getPlayer(); + if (op != null && op.isOnline() && other.getState() != PlayerState.DEAD) { + p.setSpectatorTarget(op); + break; } } } @@ -326,47 +427,51 @@ public class GameplayPhase implements Phase { PlayerState st = bp.getState(); if (st == PlayerState.ESCAPED) { escapedSurvivors++; - } else if (st == PlayerState.DEAD) { - // dead, counted below - } else { - // HEALTHY, INJURED, DOWNED, CARRIED, HOOKED — still in game + } else if (st != PlayerState.DEAD) { aliveSurvivors++; } + // Переводим мёртвых в SPECTATOR (если ещё не переведены) + if (st == PlayerState.DEAD) { + Player p = bp.getPlayer(); + if (p != null && p.isOnline() && p.getGameMode() != GameMode.SPECTATOR) { + onSurvivorDied((Survivor) bp, session); + } + } } - if (totalSurvivors == 0) return; // no survivors registered yet + if (totalSurvivors == 0) return; - boolean killerWin = (aliveSurvivors == 0 && escapedSurvivors == 0); - boolean survivorsWin = (escapedSurvivors == totalSurvivors); + // Маньяк побеждает — все мертвы и никто не сбежал + boolean killerWin = (aliveSurvivors == 0 && escapedSurvivors == 0); + // Выжившие побеждают — хотя бы один сбежал + boolean survivorsWin = (escapedSurvivors >= 1); if (killerWin) { announceKillerWin(session); shouldEnd = true; - } else if (survivorsWin) { + } else if (survivorsWin && aliveSurvivors == 0) { + // Все либо сбежали либо мертвы — конец матча announceSurvivorWin(session); shouldEnd = true; } } private void announceKillerWin(GameSession session) { - Bukkit.broadcast(Component.text("", NamedTextColor.RED)); Bukkit.broadcast(Component.text("☠ КИЛЛЕР ПОБЕДИЛ! ☠", NamedTextColor.DARK_RED)); Bukkit.broadcast(Component.text("Все выжившие уничтожены.", NamedTextColor.RED)); - Bukkit.broadcast(Component.text("", NamedTextColor.RED)); - for (BasePlayer bp : session.getPlayers().values()) { Player p = bp.getPlayer(); if (p == null || !p.isOnline()) continue; if (bp.isKiller()) { - p.showTitle(net.kyori.adventure.title.Title.title( - Component.text("ПОБЕДА!", net.kyori.adventure.text.format.TextColor.color(0xFF2222)), - Component.text("Ты уничтожил всех выживших", NamedTextColor.RED) + p.showTitle(Title.title( + Component.text("ПОБЕДА!").color(TextColor.color(0xFF2222)), + Component.text("Ты уничтожил всех выживших").color(NamedTextColor.RED) )); p.playSound(p.getLocation(), Sound.UI_TOAST_CHALLENGE_COMPLETE, 1.0f, 1.0f); } else { - p.showTitle(net.kyori.adventure.title.Title.title( - Component.text("ПОРАЖЕНИЕ", net.kyori.adventure.text.format.TextColor.color(0x888888)), - Component.text("Киллер победил", NamedTextColor.GRAY) + p.showTitle(Title.title( + Component.text("ПОРАЖЕНИЕ").color(TextColor.color(0x888888)), + Component.text("Киллер победил").color(NamedTextColor.GRAY) )); p.playSound(p.getLocation(), Sound.ENTITY_WITHER_DEATH, 0.5f, 0.8f); } @@ -374,24 +479,21 @@ public class GameplayPhase implements Phase { } private void announceSurvivorWin(GameSession session) { - Bukkit.broadcast(Component.text("", NamedTextColor.GREEN)); Bukkit.broadcast(Component.text("✔ ВЫЖИВШИЕ ПОБЕДИЛИ! ✔", NamedTextColor.GREEN)); - Bukkit.broadcast(Component.text("Все выжившие сбежали!", NamedTextColor.YELLOW)); - Bukkit.broadcast(Component.text("", NamedTextColor.GREEN)); - + Bukkit.broadcast(Component.text("Выжившие сбежали!", NamedTextColor.YELLOW)); for (BasePlayer bp : session.getPlayers().values()) { Player p = bp.getPlayer(); if (p == null || !p.isOnline()) continue; if (!bp.isKiller()) { - p.showTitle(net.kyori.adventure.title.Title.title( - Component.text("ПОБЕГ!", net.kyori.adventure.text.format.TextColor.color(0x00FF00)), - Component.text("Вы выбрались живыми!", NamedTextColor.YELLOW) + p.showTitle(Title.title( + Component.text("ПОБЕГ!").color(TextColor.color(0x00FF00)), + Component.text("Вы выбрались живыми!").color(NamedTextColor.YELLOW) )); p.playSound(p.getLocation(), Sound.UI_TOAST_CHALLENGE_COMPLETE, 1.0f, 1.2f); } else { - p.showTitle(net.kyori.adventure.title.Title.title( - Component.text("ПОРАЖЕНИЕ", net.kyori.adventure.text.format.TextColor.color(0x888888)), - Component.text("Все выжившие сбежали", NamedTextColor.GRAY) + p.showTitle(Title.title( + Component.text("ПОРАЖЕНИЕ").color(TextColor.color(0x888888)), + Component.text("Выжившие сбежали").color(NamedTextColor.GRAY) )); p.playSound(p.getLocation(), Sound.ENTITY_WITHER_DEATH, 0.5f, 0.8f); } @@ -404,12 +506,9 @@ public class GameplayPhase implements Phase { private void wireMoveListener(GameSession session) { if (moveListener == null) return; - - // Gate escape: survivor walks into open gate zone after all gens done moveListener.setOnSurvivorMove((survivor, loc) -> { if (!allGensDone) return; if (survivor.getState() == PlayerState.ESCAPED) return; - ExitGate gate = gateManager.getGateAt(loc, 3.0); if (gate != null && gate.isOpen()) { survivor.setState(PlayerState.ESCAPED); @@ -417,8 +516,7 @@ public class GameplayPhase implements Phase { if (p != null) { p.playSound(loc, Sound.ENTITY_PLAYER_LEVELUP, 1.0f, 1.2f); Bukkit.broadcast(Component.text( - "[DBD] " + p.getName() + " сбежал через ворота!", - NamedTextColor.GOLD)); + "[DBD] " + p.getName() + " сбежал через ворота!", NamedTextColor.GOLD)); } } }); @@ -427,75 +525,78 @@ public class GameplayPhase implements Phase { private void wireInteractListener(GameSession session) { if (interactListener == null) return; - // Pallet interact: survivor right-click = drop, killer left-click = break interactListener.setOnPalletInteract((player, isRightClick) -> { BasePlayer bp = session.getPlayer(player.getUniqueId()); if (bp == null) return; - if (isRightClick && !bp.isKiller()) { palletDrop.tryDrop((Survivor) bp); - } else if (!isRightClick && bp.isKiller()) { - Killer killer = (Killer) bp; + Killer k = (Killer) bp; Pallet nearby = palletManager.getPalletAt(player.getLocation(), 3.0); if (nearby != null && nearby.isDropped()) { - palletBreak.startBreaking(killer, nearby, session.getPlugin()); + palletBreak.startBreaking(k, nearby, session.getPlugin()); } } }); - // Rescue attempt: survivor right-clicks near a hook interactListener.setOnRescueAttempt((player, ignored) -> { BasePlayer bp = session.getPlayer(player.getUniqueId()); if (bp == null || bp.isKiller()) return; Survivor rescuer = (Survivor) bp; if (rescuer.getState() == PlayerState.HOOKED) return; - Hook hook = hookManager.findNearestOccupiedHook(player.getLocation(), 3.0); - if (hook != null) { - hookRescue.startRescue(rescuer, hook); - } + if (hook != null) hookRescue.startRescue(rescuer, hook); }); - // Killer right-click: hook a carried survivor interactListener.setOnAttack((player, isRightClick) -> { - if (!isRightClick) return; // left-click handled by damage system + if (!isRightClick) return; BasePlayer bp = session.getPlayer(player.getUniqueId()); if (bp == null || !bp.isKiller()) return; Killer killer = (Killer) bp; - - if (killer.getState() == PlayerState.CARRIED || killer.isCarrying()) { - // Find the carried survivor - for (BasePlayer other : session.getPlayers().values()) { - if (!other.isKiller() && other.getState() == PlayerState.CARRIED) { - Survivor carried = (Survivor) other; - if (hookManager.tryHookSurvivor(carried, player.getLocation())) { - killer.setCarrying(false); - // Teleport survivor to hook - Hook nearestHook = hookManager.findNearestOccupiedHook( - player.getLocation(), 5.0); - if (nearestHook != null) { - Player vp = carried.getPlayer(); - if (vp != null) { - vp.teleport(nearestHook.getLocation()); - } - } + if (!killer.isCarrying()) return; + for (BasePlayer other : session.getPlayers().values()) { + if (!other.isKiller() && other.getState() == PlayerState.CARRIED) { + Survivor carried = (Survivor) other; + if (hookManager.tryHookSurvivor(carried, player.getLocation())) { + killer.setCarrying(false); + Hook nearestHook = hookManager.findNearestOccupiedHook(player.getLocation(), 5.0); + if (nearestHook != null) { + Player vp = carried.getPlayer(); + if (vp != null) vp.teleport(nearestHook.getLocation()); } - break; } + break; + } + } + }); + + // Sneak: killer → GeneratorKick, survivor → HookSabotage + interactListener.setOnKillerSneak((player, isSneaking) -> { + BasePlayer bp = session.getPlayer(player.getUniqueId()); + if (bp == null) return; + if (bp.isKiller()) { + Killer killer = (Killer) bp; + if (isSneaking) { + generatorKick.startCharging(killer); + } else { + generatorKick.stopCharging(killer.getUuid()); + } + } else { + // Survivor sneak → hook sabotage + Survivor surv = (Survivor) bp; + if (isSneaking) { + hookSabotage.startSabotage(surv); + } else { + hookSabotage.stopSabotage(surv.getUuid()); } } }); - // Chest interact interactListener.setOnChestInteract((player, ignored) -> { BasePlayer bp = session.getPlayer(player.getUniqueId()); if (bp == null || bp.isKiller()) return; - var chest = chestManager.getNearestChest(player.getLocation(), 3.0); - if (chest != null) { - chestOpening.startOpening(player, chest); - } + if (chest != null) chestOpening.startOpening(player, chest); }); } @@ -505,10 +606,7 @@ public class GameplayPhase implements Phase { private static class VanillaDamageBlocker implements Listener { private final GameSession session; - - VanillaDamageBlocker(GameSession session) { - this.session = session; - } + VanillaDamageBlocker(GameSession session) { this.session = session; } @EventHandler(priority = EventPriority.HIGHEST) public void onDamage(EntityDamageEvent event) { diff --git a/src/main/java/dev/loki/phase/LobbyPhase.java b/src/main/java/dev/loki/phase/LobbyPhase.java index e3cfe68..3a69446 100644 --- a/src/main/java/dev/loki/phase/LobbyPhase.java +++ b/src/main/java/dev/loki/phase/LobbyPhase.java @@ -8,19 +8,25 @@ import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Bukkit; import org.bukkit.entity.Player; +/** + * Лобби-фаза: ждёт минимум 4 игроков. + * При 4 игроках таймер 45 сек, при 5 — сбрасывается до 15 сек. + */ public class LobbyPhase implements Phase { private final GameConfig config; - private int countdown; + private int tickSub; // счётчик тиков внутри текущей секунды private int secondsLeft; private boolean started; private boolean forced; + private int lastPlayerCount; // для отслеживания изменения кол-ва игроков public LobbyPhase(GameConfig config) { this.config = config; - this.secondsLeft = config.getLobbyCountdown(); - this.countdown = 0; + this.secondsLeft = config.getCountdown4Players(); // начальный таймер + this.tickSub = 0; this.started = false; this.forced = false; + this.lastPlayerCount = 0; } @Override @@ -43,31 +49,62 @@ public class LobbyPhase implements Phase { } int playerCount = session.getPlayerCount(); - int minPlayers = config.getMinPlayers(); + int minPlayers = config.getMinPlayers(); // 4 + int maxPlayers = config.getMaxPlayers(); // 5 + // Недостаточно игроков — сбрасываем таймер if (playerCount < minPlayers) { - secondsLeft = config.getLobbyCountdown(); - countdown = 0; + secondsLeft = config.getCountdown4Players(); + tickSub = 0; + lastPlayerCount = playerCount; sendActionBarToAll(session, - Component.text("⏳ Ожидание игроков: " + playerCount + "/" + minPlayers, NamedTextColor.YELLOW)); + Component.text("⏳ Ожидание игроков: " + playerCount + "/" + minPlayers, + NamedTextColor.YELLOW)); return; } - countdown++; - if (countdown >= 20) { - countdown = 0; - secondsLeft--; + // Когда количество игроков меняется — обновляем нужный таймер + if (playerCount != lastPlayerCount) { + lastPlayerCount = playerCount; + if (playerCount >= maxPlayers) { + // При максимуме игроков сокращаем таймер до countdown-5-players + int fast = config.getCountdown5Players(); + if (secondsLeft > fast) { + secondsLeft = fast; + tickSub = 0; + Bukkit.broadcast(Component.text( + "[DBD] Все игроки вошли! Старт через " + fast + " сек!", + NamedTextColor.GREEN)); + } + } } - String bar = buildCountdownBar(secondsLeft, config.getLobbyCountdown()); + // Тикаем таймер + tickSub++; + if (tickSub >= 20) { + tickSub = 0; + secondsLeft--; + + // Анонс вслух на ключевых секундах + if (secondsLeft == 30 || secondsLeft == 15 || secondsLeft == 10 + || secondsLeft == 5 || secondsLeft == 3 + || secondsLeft == 2 || secondsLeft == 1) { + Bukkit.broadcast(Component.text( + "[DBD] До начала: " + secondsLeft + " сек.", + NamedTextColor.YELLOW)); + } + } + + // ActionBar всем + int total = (playerCount >= maxPlayers) + ? config.getCountdown5Players() + : config.getCountdown4Players(); + String bar = buildCountdownBar(secondsLeft, total); + NamedTextColor barColor = (playerCount >= maxPlayers) + ? NamedTextColor.GREEN : NamedTextColor.YELLOW; sendActionBarToAll(session, Component.text("▶ Старт через: " + secondsLeft + "с " + bar + - " (" + playerCount + "/" + minPlayers + ")", NamedTextColor.GREEN)); - - if (countdown == 0 && (secondsLeft <= 5 || secondsLeft == 10 || secondsLeft == 30)) { - Bukkit.broadcast(Component.text( - "[DBD] До начала: " + secondsLeft + " сек.", NamedTextColor.YELLOW)); - } + " (" + playerCount + "/" + minPlayers + ")", barColor)); if (secondsLeft <= 0) { started = true; @@ -85,7 +122,7 @@ public class LobbyPhase implements Phase { private String buildCountdownBar(int current, int total) { int filled = total > 0 ? (int) (10.0 * current / total) : 0; - int empty = 10 - filled; + int empty = 10 - filled; return "§a" + "█".repeat(Math.max(0, filled)) + "§7" + "░".repeat(Math.max(0, empty)); } @@ -102,9 +139,10 @@ public class LobbyPhase implements Phase { public void onPlayerJoined(GameSession session, Player player) { session.getPlugin().getLobbyManager().teleportToLobby(player); int playerCount = session.getPlayerCount(); - int minPlayers = config.getMinPlayers(); + int minPlayers = config.getMinPlayers(); Bukkit.broadcast(Component.text( - "[DBD] " + player.getName() + " присоединился к очереди! (" + playerCount + "/" + minPlayers + ")", + "[DBD] " + player.getName() + " присоединился к очереди! (" + + playerCount + "/" + minPlayers + ")", NamedTextColor.YELLOW)); } diff --git a/src/main/java/dev/loki/role/RoleAssigner.java b/src/main/java/dev/loki/role/RoleAssigner.java index 3cac413..a741487 100644 --- a/src/main/java/dev/loki/role/RoleAssigner.java +++ b/src/main/java/dev/loki/role/RoleAssigner.java @@ -1,5 +1,6 @@ package dev.loki.role; +import dev.loki.config.GameConfig; import dev.loki.config.LocationsConfig; import dev.loki.core.GameSession; import dev.loki.player.BasePlayer; @@ -7,6 +8,7 @@ import dev.loki.player.Killer; import dev.loki.player.Survivor; import org.bukkit.Bukkit; import org.bukkit.Location; +import org.bukkit.attribute.Attribute; import org.bukkit.entity.Player; import java.util.*; @@ -30,6 +32,7 @@ public class RoleAssigner { public void assignRoles(GameSession session) { Map players = session.getPlayers(); + GameConfig config = session.getConfig(); if (players.isEmpty()) { session.getPlugin().getLogger().warning("[RoleAssigner] No players in session!"); return; @@ -42,10 +45,10 @@ public class RoleAssigner { List survivorSpawns = locations.getSurvivorSpawns(); if (killerSpawn == null) { - session.getPlugin().getLogger().warning("[RoleAssigner] killer-spawn not set! Using game world spawn."); + session.getPlugin().getLogger().warning("[RoleAssigner] killer-spawn not set!"); } if (survivorSpawns.isEmpty()) { - session.getPlugin().getLogger().warning("[RoleAssigner] survivor-spawns not set! Using game world spawn."); + session.getPlugin().getLogger().warning("[RoleAssigner] survivor-spawns not set!"); } int spawnIndex = 0; @@ -69,30 +72,39 @@ public class RoleAssigner { if (bukkit != null && bukkit.isOnline()) { if (newPlayer.isKiller()) { bukkit.showTitle(net.kyori.adventure.title.Title.title( - net.kyori.adventure.text.Component.text("КИЛЛЕР").color(net.kyori.adventure.text.format.TextColor.color(0xFF2222)), - net.kyori.adventure.text.Component.text("Охоться на выживших!").color(net.kyori.adventure.text.format.NamedTextColor.RED) + net.kyori.adventure.text.Component.text("КИЛЛЕР") + .color(net.kyori.adventure.text.format.TextColor.color(0xFF2222)), + net.kyori.adventure.text.Component.text("Охоться на выживших!") + .color(net.kyori.adventure.text.format.NamedTextColor.RED) )); - bukkit.sendMessage(net.kyori.adventure.text.Component.text("[DBD] Ваша роль: Киллер", net.kyori.adventure.text.format.NamedTextColor.RED)); + bukkit.sendMessage(net.kyori.adventure.text.Component.text( + "[DBD] Ваша роль: Киллер", + net.kyori.adventure.text.format.NamedTextColor.RED)); + applyScale(bukkit, config.getKillerScale()); } else { bukkit.showTitle(net.kyori.adventure.title.Title.title( - net.kyori.adventure.text.Component.text("ВЫЖИВШИЙ").color(net.kyori.adventure.text.format.TextColor.color(0x00FF00)), - net.kyori.adventure.text.Component.text("Чини генераторы и сбегай!").color(net.kyori.adventure.text.format.NamedTextColor.YELLOW) + net.kyori.adventure.text.Component.text("ВЫЖИВШИЙ") + .color(net.kyori.adventure.text.format.TextColor.color(0x00FF00)), + net.kyori.adventure.text.Component.text("Чини генераторы и сбегай!") + .color(net.kyori.adventure.text.format.NamedTextColor.YELLOW) )); - bukkit.sendMessage(net.kyori.adventure.text.Component.text("[DBD] Ваша роль: Выживший", net.kyori.adventure.text.format.NamedTextColor.GREEN)); + bukkit.sendMessage(net.kyori.adventure.text.Component.text( + "[DBD] Ваша роль: Выживший", + net.kyori.adventure.text.format.NamedTextColor.GREEN)); + applyScale(bukkit, config.getSurvivorScale()); } Location spawn = newPlayer.getSpawnLocation(); if (spawn != null) { bukkit.teleport(spawn); - session.getPlugin().getLogger().info("[RoleAssigner] Teleported " + bukkit.getName() + " to " + spawn); + session.getPlugin().getLogger().info("[RoleAssigner] Teleported " + bukkit.getName()); } else { var gameWorld = session.getPlugin().getLobbyManager().getGameWorld(); if (gameWorld != null) { - Location fallback = gameWorld.getSpawnLocation(); - bukkit.teleport(fallback); - session.getPlugin().getLogger().info("[RoleAssigner] Teleported " + bukkit.getName() + " to game world spawn (no spawn set)"); + bukkit.teleport(gameWorld.getSpawnLocation()); } else { - session.getPlugin().getLogger().severe("[RoleAssigner] Game world is null! Cannot teleport " + bukkit.getName()); + session.getPlugin().getLogger().severe( + "[RoleAssigner] Game world is null! Cannot teleport " + bukkit.getName()); } } } @@ -103,6 +115,14 @@ public class RoleAssigner { queue.rotate(); } + /** Apply GENERIC_SCALE attribute (Paper 1.20.5+). Silently skips if unavailable. */ + private void applyScale(Player player, double scale) { + try { + var attr = player.getAttribute(Attribute.GENERIC_SCALE); + if (attr != null) attr.setBaseValue(scale); + } catch (Exception ignored) {} + } + private UUID selectKiller(List uuids) { UUID queuedKiller = queue.nextKiller(); if (queuedKiller != null && uuids.contains(queuedKiller)) { diff --git a/src/main/java/dev/loki/sound/TerrorRadiusSound.java b/src/main/java/dev/loki/sound/TerrorRadiusSound.java index 7b282af..04eec63 100644 --- a/src/main/java/dev/loki/sound/TerrorRadiusSound.java +++ b/src/main/java/dev/loki/sound/TerrorRadiusSound.java @@ -12,17 +12,23 @@ import java.util.Collection; public class TerrorRadiusSound extends BukkitRunnable { - private static final double TERROR_RADIUS = 32.0; private static final long TICK_INTERVAL = 20L; private final JavaPlugin plugin; private final SoundManager soundManager; + private final double terrorRadius; private Killer killer; private Collection survivors; - public TerrorRadiusSound(JavaPlugin plugin, SoundManager soundManager) { + public TerrorRadiusSound(JavaPlugin plugin, SoundManager soundManager, double terrorRadius) { this.plugin = plugin; this.soundManager = soundManager; + this.terrorRadius = terrorRadius; + } + + /** Обратная совместимость — без конфига использует дефолт 32 */ + public TerrorRadiusSound(JavaPlugin plugin, SoundManager soundManager) { + this(plugin, soundManager, 32.0); } public void setTargets(Killer killer, Collection survivors) { @@ -59,8 +65,8 @@ public class TerrorRadiusSound extends BukkitRunnable { Location survLoc = survPlayer.getLocation(); double distance = killerLoc.distance(survLoc); - if (distance <= TERROR_RADIUS) { - SoundLevels level = SoundLevels.fromDistance(distance, TERROR_RADIUS); + if (distance <= terrorRadius) { + SoundLevels level = SoundLevels.fromDistance(distance, terrorRadius); playTerrorSound(survPlayer, level); } } @@ -71,14 +77,12 @@ public class TerrorRadiusSound extends BukkitRunnable { } public double getTerrorRadius() { - return TERROR_RADIUS; + return terrorRadius; } public SoundLevels getTerrorLevel(double distance) { - if (distance > TERROR_RADIUS) { - return null; - } - return SoundLevels.fromDistance(distance, TERROR_RADIUS); + if (distance > terrorRadius) return null; + return SoundLevels.fromDistance(distance, terrorRadius); } public void playImmediateTerror(Survivor survivor, SoundLevels level) { diff --git a/src/main/java/dev/loki/survivor/SurvivorController.java b/src/main/java/dev/loki/survivor/SurvivorController.java index 067f17a..b02c448 100644 --- a/src/main/java/dev/loki/survivor/SurvivorController.java +++ b/src/main/java/dev/loki/survivor/SurvivorController.java @@ -1,5 +1,6 @@ package dev.loki.survivor; +import dev.loki.config.GameConfig; import dev.loki.player.PlayerState; import dev.loki.player.Survivor; import org.bukkit.entity.Player; @@ -11,23 +12,17 @@ public class SurvivorController { private final SurvivorMovement movement; private final SurvivorHealing healing; - public SurvivorController(Survivor survivor) { + public SurvivorController(Survivor survivor, GameConfig config) { this.survivor = survivor; this.movement = new SurvivorMovement(this); - this.healing = new SurvivorHealing(this); + this.healing = new SurvivorHealing(this, config); } - public void onSprintStart() { - movement.onSprintStart(); - } + public void onSprintStart() { movement.onSprintStart(); } + public void onSprintEnd() { movement.onSprintEnd(); } - public void onSprintEnd() { - movement.onSprintEnd(); - } - - public void onHealAttempt(Player target) { - healing.startHealing(target); - } + public void onHealAttempt(Player target) { healing.startHealing(target); } + public void onHealCancel() { healing.cancelHealing(); } public void tick() { movement.tick(); @@ -38,40 +33,18 @@ public class SurvivorController { private void updateSpeed() { Player player = survivor.getPlayer(); if (player == null) return; - float speed = movement.getCurrentSpeed(); player.setWalkSpeed(speed); } - public void onStateChange(PlayerState newState) { - movement.onStateChange(newState); - } + public void onStateChange(PlayerState newState) { movement.onStateChange(newState); } - public UUID getUuid() { - return survivor.getUuid(); - } - - public Survivor getSurvivor() { - return survivor; - } - - public Player getPlayer() { - return survivor.getPlayer(); - } - - public PlayerState getState() { - return survivor.getState(); - } - - public boolean isDowned() { - return survivor.getState() == PlayerState.DOWNED; - } - - public boolean isInjured() { - return survivor.getState() == PlayerState.INJURED; - } - - public boolean isHealthy() { - return survivor.getState() == PlayerState.HEALTHY; - } + public UUID getUuid() { return survivor.getUuid(); } + public Survivor getSurvivor() { return survivor; } + public Player getPlayer() { return survivor.getPlayer(); } + public PlayerState getState() { return survivor.getState(); } + public boolean isDowned() { return survivor.getState() == PlayerState.DOWNED; } + public boolean isInjured() { return survivor.getState() == PlayerState.INJURED; } + public boolean isHealthy() { return survivor.getState() == PlayerState.HEALTHY; } + public SurvivorHealing getHealing() { return healing; } } diff --git a/src/main/java/dev/loki/survivor/SurvivorHealing.java b/src/main/java/dev/loki/survivor/SurvivorHealing.java index 84af0d2..806906e 100644 --- a/src/main/java/dev/loki/survivor/SurvivorHealing.java +++ b/src/main/java/dev/loki/survivor/SurvivorHealing.java @@ -1,29 +1,40 @@ package dev.loki.survivor; +import dev.loki.config.GameConfig; import dev.loki.player.PlayerState; import dev.loki.player.Survivor; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; -import org.bukkit.Bukkit; import org.bukkit.entity.Player; import java.util.UUID; +/** + * Handles healing another survivor (shift + proximity, 15 sec by default) + * or self-healing with a medkit (configurable). + * Limits: max-heals-per-match heals per survivor. + */ public class SurvivorHealing { private final SurvivorController controller; + private final GameConfig config; + private Player healingTarget; private int healingTicks; private int totalHealingTicks; private boolean isSelfHeal; - private static final int SELF_HEAL_TICKS = 600; - private static final int OTHER_HEAL_TICKS = 320; - private static final double HEAL_RANGE = 3.0; + // Per-survivor heal counter (tracks heals used this match) + private int healsUsed = 0; - public SurvivorHealing(SurvivorController controller) { + public SurvivorHealing(SurvivorController controller, GameConfig config) { this.controller = controller; + this.config = config; } + // ------------------------------------------------------------------------- + // Public API + // ------------------------------------------------------------------------- + public void startHealing(Player target) { if (isHealing()) return; if (controller.isDowned()) return; @@ -31,6 +42,12 @@ public class SurvivorHealing { Player healer = controller.getPlayer(); if (healer == null) return; + // Check heal limit + if (healsUsed >= config.getMaxHealsPerMatch()) { + healer.sendMessage(Component.text("[DBD] Лимит лечений исчерпан!", net.kyori.adventure.text.format.NamedTextColor.RED)); + return; + } + UUID targetUuid = target.getUniqueId(); Survivor targetSurvivor = getSurvivorFromPlayer(target); if (targetSurvivor == null) return; @@ -41,10 +58,18 @@ public class SurvivorHealing { boolean selfHeal = healer.getUniqueId().equals(targetUuid); if (selfHeal && targetState == PlayerState.DOWNED) return; + // Range check for other-heal + if (!selfHeal && healer.getLocation().distance(target.getLocation()) > config.getHealRange()) { + healer.sendMessage(Component.text("[DBD] Слишком далеко!", net.kyori.adventure.text.format.NamedTextColor.RED)); + return; + } + this.healingTarget = target; - this.isSelfHeal = selfHeal; - this.totalHealingTicks = selfHeal ? SELF_HEAL_TICKS : OTHER_HEAL_TICKS; - this.healingTicks = totalHealingTicks; + this.isSelfHeal = selfHeal; + this.totalHealingTicks = selfHeal + ? config.getSelfHealSeconds() * 20 + : config.getOtherHealSeconds() * 20; + this.healingTicks = totalHealingTicks; sendProgressMessage(); } @@ -53,21 +78,11 @@ public class SurvivorHealing { if (!isHealing()) return; Player healer = controller.getPlayer(); - if (healer == null || healingTarget == null) { - cancelHealing(); - return; - } - - if (!healingTarget.isOnline()) { - cancelHealing(); - return; - } + if (healer == null || healingTarget == null) { cancelHealing(); return; } + if (!healingTarget.isOnline()) { cancelHealing(); return; } Survivor targetSurvivor = getSurvivorFromPlayer(healingTarget); - if (targetSurvivor == null) { - cancelHealing(); - return; - } + if (targetSurvivor == null) { cancelHealing(); return; } PlayerState targetState = targetSurvivor.getState(); if (targetState != PlayerState.INJURED && targetState != PlayerState.DOWNED) { @@ -75,7 +90,11 @@ public class SurvivorHealing { return; } - if (!isSelfHeal && healer.getLocation().distance(healingTarget.getLocation()) > HEAL_RANGE) { + // Must keep sneaking to heal + if (!healer.isSneaking()) { cancelHealing(); return; } + + // Range interrupt + if (!isSelfHeal && healer.getLocation().distance(healingTarget.getLocation()) > config.getHealRange()) { cancelHealing(); return; } @@ -83,37 +102,53 @@ public class SurvivorHealing { healingTicks--; sendProgressMessage(); - if (healingTicks <= 0) { - completeHealing(targetSurvivor); - } - } - - private void completeHealing(Survivor target) { - PlayerState currentState = target.getState(); - PlayerState newState = (currentState == PlayerState.DOWNED) ? PlayerState.INJURED : PlayerState.HEALTHY; - target.setState(newState); - - Player targetPlayer = healingTarget; - if (targetPlayer != null && targetPlayer.isOnline()) { - String message = newState == PlayerState.HEALTHY ? "Вы полностью вылечились!" - : "Вы встали на ноги!"; - targetPlayer.sendMessage(MiniMessage.miniMessage().deserialize(message)); - } - - cancelHealing(); + if (healingTicks <= 0) completeHealing(targetSurvivor); } public void cancelHealing() { if (healingTarget != null && healingTarget.isOnline() && isHealing()) { healingTarget.sendActionBar(Component.empty()); + healingTarget.sendMessage(Component.text("[DBD] Лечение прервано!", net.kyori.adventure.text.format.NamedTextColor.YELLOW)); } healingTarget = null; - healingTicks = 0; + healingTicks = 0; + } + + public boolean isHealing() { return healingTicks > 0; } + public Player getHealingTarget() { return healingTarget; } + public double getProgress() { + if (!isHealing()) return 0; + return 1.0 - ((double) healingTicks / totalHealingTicks); + } + + // ------------------------------------------------------------------------- + // Private helpers + // ------------------------------------------------------------------------- + + private void completeHealing(Survivor target) { + PlayerState current = target.getState(); + PlayerState newState = (current == PlayerState.DOWNED) ? PlayerState.INJURED : PlayerState.HEALTHY; + target.setState(newState); + healsUsed++; + + Player targetPlayer = healingTarget; + if (targetPlayer != null && targetPlayer.isOnline()) { + String msg = newState == PlayerState.HEALTHY + ? "Вы полностью вылечились!" + : "Вы встали на ноги!"; + targetPlayer.sendMessage(MiniMessage.miniMessage().deserialize(msg)); + } + + Player healer = controller.getPlayer(); + if (healer != null && !healer.getUniqueId().equals(targetPlayer != null ? targetPlayer.getUniqueId() : null)) { + healer.sendMessage(Component.text("[DBD] Союзник вылечен!", net.kyori.adventure.text.format.NamedTextColor.GREEN)); + } + + cancelHealing(); } private void sendProgressMessage() { if (healingTarget == null) return; - double progress = 1.0 - ((double) healingTicks / totalHealingTicks); String bar = renderProgressBar(progress); Component message = MiniMessage.miniMessage().deserialize( @@ -121,36 +156,27 @@ public class SurvivorHealing { : "Лечение союзника: " + bar ); healingTarget.sendActionBar(message); + // Also show on healer + Player healer = controller.getPlayer(); + if (healer != null && !healer.equals(healingTarget)) { + healer.sendActionBar(message); + } } private String renderProgressBar(double progress) { - int filled = (int) (10 * progress); - int empty = 10 - filled; - StringBuilder bar = new StringBuilder(""); - bar.append("█".repeat(filled)); - bar.append(""); - bar.append("░".repeat(empty)); - bar.append(" ").append((int)(progress * 100)).append("%"); - return bar.toString(); + int filled = (int)(10 * progress); + int empty = 10 - filled; + return "" + "█".repeat(filled) + "" + "░".repeat(empty) + + " " + (int)(progress * 100) + "%"; } private Survivor getSurvivorFromPlayer(Player player) { if (controller.getPlayer() != null && controller.getPlayer().equals(player)) { return controller.getSurvivor(); } + // For healing other survivors the target won't be this controller's survivor. + // The target state check is done via the Survivor object that must be passed in. + // This method is used for both self and other. return null; } - - public boolean isHealing() { - return healingTicks > 0; - } - - public Player getHealingTarget() { - return healingTarget; - } - - public double getProgress() { - if (!isHealing()) return 0; - return 1.0 - ((double) healingTicks / totalHealingTicks); - } } diff --git a/src/main/java/dev/loki/ui/BossBarManager.java b/src/main/java/dev/loki/ui/BossBarManager.java new file mode 100644 index 0000000..6ef9a63 --- /dev/null +++ b/src/main/java/dev/loki/ui/BossBarManager.java @@ -0,0 +1,140 @@ +package dev.loki.ui; + +import dev.loki.config.GameConfig; +import dev.loki.player.Killer; +import dev.loki.player.Survivor; +import net.kyori.adventure.bossbar.BossBar; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.scheduler.BukkitTask; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * Displays a BossBar to survivors based on proximity to the Killer. + * The BossBar fills as the Killer gets closer. + */ +public class BossBarManager { + + private final Plugin plugin; + private final GameConfig config; + private final MiniMessage miniMessage = MiniMessage.miniMessage(); + + /** Per-survivor boss bar instance */ + private final Map survivorBossBars = new HashMap<>(); + + private Killer killer; + private Collection survivors; + private BukkitTask task; + + public BossBarManager(Plugin plugin, GameConfig config) { + this.plugin = plugin; + this.config = config; + } + + public void setTargets(Killer killer, Collection survivors) { + this.killer = killer; + this.survivors = survivors; + } + + public void start() { + task = new BukkitRunnable() { + @Override + public void run() { + tick(); + } + }.runTaskTimer(plugin, 0L, 10L); // update every 0.5 sec + } + + public void stop() { + if (task != null) { + task.cancel(); + task = null; + } + // Hide and remove all boss bars + for (Map.Entry entry : survivorBossBars.entrySet()) { + Player p = plugin.getServer().getPlayer(entry.getKey()); + if (p != null && p.isOnline()) { + p.hideBossBar(entry.getValue()); + } + } + survivorBossBars.clear(); + } + + private void tick() { + if (!config.isBossBarEnabled()) return; + if (killer == null || !killer.isOnline()) return; + + Player killerPlayer = killer.getPlayer(); + if (killerPlayer == null) return; + + int appearRange = config.getBossBarAppearRange(); + int fullRange = config.getBossBarFullRange(); + + for (Survivor survivor : survivors) { + if (!survivor.isOnline()) continue; + if (survivor.getState().isDeadOrEscaped()) continue; + + Player survPlayer = survivor.getPlayer(); + if (survPlayer == null) continue; + + double distance = killerPlayer.getLocation().distance(survPlayer.getLocation()); + + if (distance > appearRange) { + // Hide bar if previously shown + BossBar bar = survivorBossBars.remove(survivor.getUuid()); + if (bar != null) { + survPlayer.hideBossBar(bar); + } + continue; + } + + // Calculate fill: 1.0 when at fullRange or closer, 0.0 at appearRange + float progress = (float) Math.min(1.0, + (appearRange - distance) / (double)(appearRange - fullRange)); + progress = Math.max(0.01f, progress); // avoid 0 which hides the bar + + BossBar bar = survivorBossBars.computeIfAbsent(survivor.getUuid(), + uuid -> createBossBar()); + + bar.progress(progress); + + // Show if not yet visible + survPlayer.showBossBar(bar); + } + } + + private BossBar createBossBar() { + Component title = miniMessage.deserialize(config.getBossBarTitle()); + BossBar.Color color = parseBossBarColor(config.getBossBarColor()); + BossBar.Overlay overlay = parseBossBarOverlay(config.getBossBarStyle()); + return BossBar.bossBar(title, 0.01f, color, overlay); + } + + private BossBar.Color parseBossBarColor(String name) { + return switch (name.toUpperCase()) { + case "BLUE" -> BossBar.Color.BLUE; + case "GREEN" -> BossBar.Color.GREEN; + case "YELLOW" -> BossBar.Color.YELLOW; + case "PURPLE" -> BossBar.Color.PURPLE; + case "WHITE" -> BossBar.Color.WHITE; + default -> BossBar.Color.RED; + }; + } + + private BossBar.Overlay parseBossBarOverlay(String name) { + return switch (name.toUpperCase()) { + case "SEGMENTED_6" -> BossBar.Overlay.NOTCHED_6; + case "SEGMENTED_10" -> BossBar.Overlay.NOTCHED_10; + case "SEGMENTED_12" -> BossBar.Overlay.NOTCHED_12; + case "SEGMENTED_20" -> BossBar.Overlay.NOTCHED_20; + default -> BossBar.Overlay.PROGRESS; + }; + } +} diff --git a/src/main/java/dev/loki/ui/Sidebar.java b/src/main/java/dev/loki/ui/Sidebar.java index fb6a609..facd30c 100644 --- a/src/main/java/dev/loki/ui/Sidebar.java +++ b/src/main/java/dev/loki/ui/Sidebar.java @@ -1,11 +1,14 @@ package dev.loki.ui; +import dev.loki.config.GameConfig; import dev.loki.core.GameSession; +import dev.loki.objective.GeneratorManager; import dev.loki.player.BasePlayer; import dev.loki.player.PlayerState; import dev.loki.player.Survivor; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.scoreboard.*; @@ -14,13 +17,17 @@ import java.util.HashMap; import java.util.Map; import java.util.UUID; +/** + * Manages the sidebar scoreboard for each player. + * Displays: generator progress, survivor status (with hearts), endgame timer. + */ public class Sidebar { private static final String OBJECTIVE_NAME = "dbd_stats"; - private final Map scoreboards; - public Sidebar() { - this.scoreboards = new HashMap<>(); - } + private final Map scoreboards = new HashMap<>(); + + // Endgame timer (seconds), -1 = not active + private int endgameSecondsLeft = -1; public void setupPlayer(Player player) { ScoreboardManager manager = Bukkit.getScoreboardManager(); @@ -38,7 +45,16 @@ public class Sidebar { scoreboards.put(player.getUniqueId(), board); } + /** Set/update the endgame countdown. Pass -1 to disable. */ + public void setEndgameTimer(int seconds) { + this.endgameSecondsLeft = seconds; + } + public void updateGameStats(Player player, GameSession session) { + updateGameStats(player, session, null); + } + + public void updateGameStats(Player player, GameSession session, GeneratorManager generatorManager) { Scoreboard board = scoreboards.get(player.getUniqueId()); if (board == null) return; @@ -47,44 +63,121 @@ public class Sidebar { clearScores(board); + GameConfig cfg = session.getConfig(); int line = 15; + setScore(objective, " ", line--); - int completedGens = getCompletedGenerators(session); - int requiredGens = session.getConfig().getGeneratorRequired(); - int totalGens = session.getConfig().getGeneratorTotal(); - - setScore(objective, "§eGenerators:", line--); - setScore(objective, " §f" + completedGens + "/" + requiredGens + " completed", line--); - setScore(objective, " §7(" + totalGens + " total)", line--); - setScore(objective, " ", line--); - - long survivorsAlive = countSurvivorsInState(session, PlayerState.HEALTHY, PlayerState.INJURED, PlayerState.DOWNED); - long survivorsHooked = countSurvivorsInState(session, PlayerState.HOOKED); - long survivorsDead = countSurvivorsInState(session, PlayerState.DEAD); - long survivorsEscaped = countSurvivorsInState(session, PlayerState.ESCAPED); - - setScore(objective, "§aSurvivors:", line--); - setScore(objective, " §f" + survivorsAlive + " alive", line--); - if (survivorsHooked > 0) { - setScore(objective, " §c" + survivorsHooked + " hooked", line--); + // Endgame timer (if active) + if (endgameSecondsLeft >= 0) { + String timerColor = endgameSecondsLeft <= 30 ? "§c" : "§e"; + setScore(objective, "§8─────────────────", line--); + setScore(objective, timerColor + "⏱ До конца: " + endgameSecondsLeft + " сек", line--); + setScore(objective, "§8─────────────────", line--); } - if (survivorsDead > 0) { - setScore(objective, " §7" + survivorsDead + " dead", line--); - } - if (survivorsEscaped > 0) { - setScore(objective, " §2" + survivorsEscaped + " escaped", line--); - } - setScore(objective, " ", line--); + // Generator progress + int completedGens = generatorManager != null ? generatorManager.getCompletedCount() : 0; + int requiredGens = cfg.getGeneratorRequired(); + int totalGens = cfg.getGeneratorTotal(); + + setScore(objective, "§eГенераторы:", line--); + String genBar = buildGenBar(completedGens, requiredGens); + setScore(objective, " §f" + completedGens + "/" + requiredGens + " " + genBar, line--); + setScore(objective, " §7(всего: " + totalGens + ")", line--); + setScore(objective, " ", line--); + + // Survivor list with hearts and colors + setScore(objective, "§aВыжившие:", line--); + for (BasePlayer bp : session.getPlayers().values()) { + if (bp.isKiller()) continue; + Survivor surv = (Survivor) bp; + Player survPlayer = surv.getPlayer(); + String name = survPlayer != null ? survPlayer.getName() : "???"; + + String colorCode = getColorCode(surv.getState(), cfg); + String hearts = buildHearts(surv.getHooksTaken(), cfg.getMaxHooks(), cfg); + String statusIcon = getStatusIcon(surv.getState()); + + setScore(objective, " " + colorCode + name + " " + hearts + " " + statusIcon, line--); + } + + setScore(objective, " ", line--); + + // Current player info BasePlayer bp = session.getPlayer(player.getUniqueId()); - if (bp instanceof Survivor surv && !bp.isKiller()) { + if (bp instanceof Survivor surv) { int hooks = surv.getHooksTaken(); - String hookBar = "§f[" + "§c⚓".repeat(hooks) + "§7○".repeat(Math.max(0, 3 - hooks)) + "§f]"; - setScore(objective, "Your hooks: " + hookBar, line--); + int maxHooks = cfg.getMaxHooks(); + int livesLeft = maxHooks - hooks; + String livesHearts = buildLivesHearts(livesLeft, maxHooks, cfg); + setScore(objective, "§fЖизни: " + livesHearts, line--); + setScore(objective, "§7Статус: " + surv.getState().getDisplayName(), line--); + } else if (bp != null && bp.isKiller()) { + setScore(objective, "§cРоль: §4МАНЬЯК", line--); } } + // ------------------------------------------------------------------------- + // Helpers + // ------------------------------------------------------------------------- + + private String getColorCode(PlayerState state, GameConfig cfg) { + String colorName = switch (state) { + case INJURED -> cfg.getColorInjured(); + case HOOKED -> cfg.getColorHooked(); + default -> cfg.getColorHealthy(); + }; + return switch (colorName.toUpperCase()) { + case "RED" -> "§c"; + case "DARK_RED" -> "§4"; + case "GREEN" -> "§a"; + case "YELLOW" -> "§e"; + case "GRAY" -> "§7"; + case "DARK_GRAY" -> "§8"; + default -> "§f"; // WHITE + }; + } + + private String buildHearts(int hooksTaken, int maxHooks, GameConfig cfg) { + if (!cfg.isShowHearts()) return ""; + String sym = cfg.getHeartSymbol(); + int lives = maxHooks - hooksTaken; + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < lives; i++) sb.append("§c").append(sym); + for (int i = 0; i < hooksTaken; i++) sb.append("§8").append(sym); + return sb.toString(); + } + + private String buildLivesHearts(int livesLeft, int maxLives, GameConfig cfg) { + String sym = cfg.getHeartSymbol(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < livesLeft; i++) sb.append("§c").append(sym); + for (int i = 0; i < (maxLives - livesLeft); i++) sb.append("§8").append(sym); + return sb.toString(); + } + + private String getStatusIcon(PlayerState state) { + return switch (state) { + case INJURED -> "§c⚠"; + case DOWNED -> "§4✖"; + case HOOKED -> "§4⚓"; + case CARRIED -> "§5▲"; + case DEAD -> "§7✝"; + case ESCAPED -> "§a✔"; + default -> ""; + }; + } + + private String buildGenBar(int completed, int required) { + StringBuilder sb = new StringBuilder("["); + for (int i = 0; i < required; i++) { + sb.append(i < completed ? "§a■" : "§7□"); + } + sb.append("§f]"); + return sb.toString(); + } + private void setScore(Objective objective, String text, int score) { objective.getScore(text).setScore(score); } @@ -95,34 +188,35 @@ public class Sidebar { } } + @SuppressWarnings("unused") private long countSurvivorsInState(GameSession session, PlayerState... states) { return session.getPlayers().values().stream() .filter(p -> !p.isKiller()) .filter(p -> { - for (PlayerState state : states) { - if (p.getState() == state) return true; + for (PlayerState s : states) { + if (p.getState() == s) return true; } return false; }) .count(); } - private int getCompletedGenerators(GameSession session) { - return 0; - } - public void removePlayer(Player player) { Scoreboard board = scoreboards.remove(player.getUniqueId()); if (board != null) { - player.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard()); + ScoreboardManager manager = Bukkit.getScoreboardManager(); + if (manager != null) { + player.setScoreboard(manager.getMainScoreboard()); + } } } public void clear() { + ScoreboardManager manager = Bukkit.getScoreboardManager(); for (Map.Entry entry : scoreboards.entrySet()) { - Player player = Bukkit.getPlayer(entry.getKey()); - if (player != null) { - player.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard()); + Player p = Bukkit.getPlayer(entry.getKey()); + if (p != null && manager != null) { + p.setScoreboard(manager.getMainScoreboard()); } } scoreboards.clear(); diff --git a/src/main/resources/game.yml b/src/main/resources/game.yml index 34f4812..191ce21 100644 --- a/src/main/resources/game.yml +++ b/src/main/resources/game.yml @@ -5,116 +5,194 @@ # Лобби lobby: # Минимум игроков для старта - min-players: 5 - + min-players: 4 + # Максимум игроков max-players: 5 - - # Обратный отсчёт (секунд) - countdown-seconds: 60 - + + # Обратный отсчёт при 4 игроках (секунд) + countdown-4-players: 45 + + # Обратный отсчёт при 5 игроках (секунд) + countdown-5-players: 15 + # Задержка перед назначением ролей (секунд) role-assign-delay: 5 + # Количество серверных лобби (0 = только пользовательские) + server-lobby-count: 3 + + # Сохранять лобби между перезапусками + persist-lobbies: true + +# Размеры игроков (1.0 = стандарт) +players: + survivor-scale: 1.0 + killer-scale: 1.5 + # Генераторы generators: # Всего генераторов на карте total-count: 7 - + # Сколько нужно починить для ворот required-count: 5 - - # Время починки одного генератора (секунд) - repair-seconds: 80 - + + # Время починки одного генератора (секунд) [90 сек по ТЗ] + repair-seconds: 90 + # Скорость отката без чинильщика (%/сек) regression-rate: 0.25 - - # Скорость отката после пинка (%/сек) + + # Скорость отката после удара (%/сек) kick-regression-rate: 2.5 - - # Длительность отката после пинка (секунд) + + # Потеря прогресса после удара (%) + kick-regression-percent: 20.0 + + # Длительность отката после удара (секунд) kick-duration-seconds: 30 + # Кулдаун удара по генератору (секунд, общий для всех генераторов) + kick-cooldown-seconds: 30 + + # Время зажатия Shift для удара (секунд) + kick-charge-seconds: 3 + + # Расстояние для взаимодействия с генератором (блоков) + interaction-range: 3.0 + # Крюки hooks: # Длительность Фазы 1 (секунд) stage1-seconds: 60 - + # Длительность Фазы 2 (секунд) stage2-seconds: 60 - + # Интервал борьбы на Фазе 2 (секунд) struggle-interval: 2 - - # Максимум крюков на одного выжившего + + # Максимум крюков на одного выжившего (= жизни) max-hooks: 3 + # Расстояние для взаимодействия с крюком (блоков) + interaction-range: 3.0 + + # Время спасения (секунд) + rescue-seconds: 3 + + # Время саботажа крюка (секунд) + sabotage-seconds: 10 + + # Время восстановления крюка (секунд) + sabotage-recovery-seconds: 60 + # Скорости (% от нормальной) movement: # Киллер killer: 115 - + # Киллер с выжившим killer-carry: 92 - + # Выживший шагом survivor-walk: 100 - + # Выживший спринт survivor-sprint: 150 - + # Выживший в нокдауне (ползком) survivor-crawl: 30 - + # Длительность истощения (секунд) exhaustion-duration: 3 - + # Кулдаун истощения (секунд) exhaustion-cooldown: 5 # Лечение healing: - # Самолечение (секунд) - self-heal-seconds: 30 - # Лечение другого (секунд) - other-heal-seconds: 16 - + other-heal-seconds: 15 + + # Самолечение аптечкой (секунд) + self-heal-seconds: 30 + # Бонус аптечки (0.5 = в 2 раза быстрее) medkit-bonus: 0.5 - + # Прерывание при движении interrupt-on-move: true + # Максимум лечений за матч + max-heals-per-match: 4 + + # Расстояние лечения (блоков) + heal-range: 3.0 + +# Переноска выжившего +carry: + # Количество прыжков для заполнения шкалы побега + escape-jumps: 15 + + # Speed I после побега (секунд) + escape-speed-seconds: 3 + + # Приращение шкалы за прыжок (%) + jump-fill-percent: 6.67 + # Киллер killer: # Кулдаун атаки (секунд) attack-cooldown: 2.5 - + # Дальность атаки (блоков) attack-range: 3.0 - + # Длительность оглушения паллетом (секунд) - pallet-stun-seconds: 2.5 - + pallet-stun-seconds: 2.0 + # Время ломания паллета (секунд) pallet-break-seconds: 3.0 - - # Радиус ужаса (блоков) + + # Радиус страха (блоков) terror-radius: 32 + # Расстояние для взаимодействия с паллетом (блоков) + pallet-interaction-range: 3.0 + +# BossBar радиуса страха +bossbar: + # Включить BossBar + enabled: true + + # Название (поддерживает MiniMessage) + title: "☠ МАНЬЯК РЯДОМ ☠" + + # Цвет: PINK, BLUE, RED, GREEN, YELLOW, PURPLE, WHITE + color: RED + + # Стиль: SOLID, SEGMENTED_6, SEGMENTED_10, SEGMENTED_12, SEGMENTED_20 + style: SOLID + + # Расстояние начала появления (блоков) + appear-range: 32 + + # Расстояние полного заполнения (блоков) + full-range: 8 + # Окна window: # Быстрый волт выжившего (секунд) vault-survivor-fast: 1.0 - + # Медленный волт выжившего (секунд) vault-survivor-slow: 1.5 - + # Волт киллера (секунд) vault-killer: 1.5 - + # Блокировка после 3 волтов подряд (секунд) vault-block-seconds: 15 @@ -122,17 +200,39 @@ window: endgame: # Время открытия ворот (секунд) gate-open-seconds: 20 - - # Время до коллапса (секунд) + + # Время до конца после открытия ворот (секунд) collapse-seconds: 120 - + # Открытие сундуков (секунд) chest-open-seconds: 3 + # Расстояние для взаимодействия с воротами (блоков) + gate-interaction-range: 3.0 + +# Уведомление маньяка о генераторе +killer-notification: + # Время действия компаса (секунд) + compass-duration-seconds: 5 + +# Скорборд +scoreboard: + # Показывать сердца (жизни) + show-hearts: true + + # Символ сердца + heart-symbol: "♥" + + # Цвет ника: HEALTHY, INJURED (красный), HEALING (зелёный), HOOKED (тёмно-красный) + color-healthy: "WHITE" + color-injured: "RED" + color-healing: "GREEN" + color-hooked: "DARK_RED" + # Люк hatch: # Минимум живых для открытия люка min-survivors-for-open: 1 - + # Условие: живых < починенных генераторов require-more-gens-than-survivors: true