fix: remove Hardcore mode, disable Elytra by default, add bug tracker
- Remove HardcoreMode (lose all rewards on fall) - Disable Elytra mode in default config - Add todo.md with 20 identified bugs - Fix 3 remaining bugs: checkPlayerFall continue, removeTrailBlocks sync, scoredBlocks clear on fall
This commit is contained in:
parent
bee00c0c81
commit
0079cbf54b
8 changed files with 87 additions and 405 deletions
|
|
@ -45,7 +45,7 @@ Output: `build/libs/LoParkour-1.3.0.jar`
|
|||
## ✨ Features
|
||||
|
||||
- **Infinite Generation** - Parkour generates endlessly as you play
|
||||
- **Multiple Game Modes** - Default, Speedrun, Hardcore, Gravity Shift, Elytra
|
||||
- **Multiple Game Modes** - Default, Speedrun, Gravity Shift, Elytra
|
||||
- **Ghost System** - Race against top player recordings
|
||||
- **Custom Schematics** - Create and use your own parkour structures
|
||||
- **Visual Themes** - Customize block styles and particle effects
|
||||
|
|
@ -57,7 +57,6 @@ Output: `build/libs/LoParkour-1.3.0.jar`
|
|||
|
||||
- **Default** - Classic infinite parkour
|
||||
- **Speedrun** - Blocks disappear after a few seconds
|
||||
- **Hardcore** - Lose all rewards on fall
|
||||
- **Gravity Shift** - Random gravity effects (levitation, slow falling, jump boost)
|
||||
- **Elytra** - Fly through particle rings
|
||||
|
||||
|
|
|
|||
385
TODO.md
385
TODO.md
|
|
@ -1,324 +1,109 @@
|
|||
# LoParkour — TODO (обновлено 2026-06-01)
|
||||
# LoParkour Bug Tracker
|
||||
|
||||
> Roadmap + **честный аудит** репозитория. Старые галочки «✅» в фазах 1–4 не соответствуют коду — см. раздел **Аудит**.
|
||||
## CRITICAL
|
||||
|
||||
## Легенда
|
||||
### 1. ProfileValue.asInt/asDouble — NumberFormatException при пустой строке
|
||||
- **Файл:** `Profile.java:57-58`
|
||||
- **Описание:** `Integer.parseInt(value)` и `Double.parseDouble(value)` падают при пустой строке `""`, которая возвращается по умолчанию при отсутствии ключа (`new ProfileValue("")`)
|
||||
- **Фикс:** Добавить дефолтное значение либо кидать понятное исключение, а не NFE
|
||||
|
||||
- 🔴 Критично — блокирует сборку / релиз / основной UX
|
||||
- 🟡 Важно — заявленная функциональность или i18n
|
||||
- 🟢 Планово — улучшение, рефакторинг
|
||||
- ✅ Готово (проверено в коде)
|
||||
### 2. Bukkit.createBlockData — IllegalArgumentException при невалидном materialName
|
||||
- **Файл:** `GeneratorProfileManager.java:133`
|
||||
- **Описание:** Если materialName из конфига невалидный (опечатка в generation.yml), `Bukkit.createBlockData()` кинет исключение
|
||||
- **Фикс:** Валидировать materialName перед вызовом, либо ловить конкретно на строке 133
|
||||
|
||||
---
|
||||
### 3. JumpDirector диагональный дрейф — heading никогда не выпрямляется
|
||||
- **Файл:** `JumpDirector.java:87-99`
|
||||
- **Описание:** `getRecommendedHeading()` всегда создает `Vector(1,0,1)` для X и Z границ. После нормализации heading становится диагональным и НИКОГДА не возвращается к осевому направлению
|
||||
- **Фикс:** Использовать `Vector(1,0,0)` для X и `Vector(0,0,1)` для Z. Сбрасывать heading на исходный при отсутствии границы
|
||||
|
||||
## Аудит 2026-06-01 (снимок)
|
||||
### 4. subList().clear() на synchronizedList(LinkedList) — повреждение node связей
|
||||
- **Файл:** `GeneratorCleanup.java:50-56`
|
||||
- **Описание:** `history.subList(0, removeCount).clear()` на `Collections.synchronizedList(new LinkedList<>())` — compound операция не атомарна. ConcurrentModificationException или повреждение связей
|
||||
- **Фикс:** Оборачивать в `synchronized(history)` или использовать CopyOnWriteArrayList
|
||||
|
||||
| Область | Факт |
|
||||
|--------|------|
|
||||
| **Сборка** | ✅ `./gradlew compileJava` проходит |
|
||||
| **Адаптивность** | 🟡 Подключено: `AdaptiveServices`, `MetricsCollector` на генераторе, профиль после fall/reset |
|
||||
| **Elytra** | 🟡 `ElytraMode` + `ElytraGenerator` есть; отдельного `mode/elytra/*` из старого TODO **нет** |
|
||||
| **Размер файлов** | 🔴 Цель «все < 200 строк» **не выполнена**: 18 файлов > 150, 2 > 200 |
|
||||
| **i18n** | 🟡 Основные потоки через `Locales`; остаётся Phase 0 split + `MessageKeys` |
|
||||
| **GUI** | 🟡 Основные меню на LoLib OK; **заглушки** `Menu`/`PagedMenu`, `DynamicMenu` вырезан |
|
||||
| **TODO.md** | Ранее помечено ✅ то, что не сделано — статусы ниже исправлены |
|
||||
### 5. Мультиплеер — runaway генерация блоков
|
||||
- **Файл:** `PlayerInteractionHandler.java:116-118` + `LifecycleTickManager.java:89-97`
|
||||
- **Описание:** Каждый игрок триггерит `generate(1)` при скоре. N игроков = N блоков/тик. maintainBlockLead добавляет еще. Положительная обратная связь
|
||||
- **Фикс:** Не больше 1 блока за тик независимо от числа игроков, кэп на maintainBlockLead
|
||||
|
||||
**Статистика:** ~160 `.java` файлов, средний размер ~85 строк, максимум **252** (`ElytraGenerator.java`).
|
||||
### 6. Система схематик полностью сломана
|
||||
- **Файл:** `PlayerInteractionHandler.java:111-113` + `BlockPlacer.java:207-212`
|
||||
- **Описание:** `isSchematicEndBlock` возвращает true для ЛЮБОГО schematic блока. При входе в схематик сразу удаляется + блоки засчитываются как очки
|
||||
- **Фикс:** `isSchematicEndBlock` должен проверять только ПОСЛЕДНИЙ блок схематика. Исключить schematic блоки из скоринга
|
||||
|
||||
---
|
||||
### 7. Прыжки за границами валидации
|
||||
- **Файл:** `JumpCalculator.java:101-114`
|
||||
- **Описание:** JumpOffsetGenerator генерирует offset до 5 блоков, total horizontal = sqrt(4²+5²)=6.4 при лимите 4.1. Retry-цикл не гарантирует валидность за 10 попыток
|
||||
- **Фикс:** Ограничить randomOffset пределами валидатора: `maxRandomOffset = sqrt(maxHorizontal² - distance²)`
|
||||
|
||||
## Фаза 0: Полный рефакторинг (KISS · DRY · SOLID) — приоритет
|
||||
## HIGH
|
||||
|
||||
> Цель: предсказуемая структура, тонкие классы, без «божественных» файлов.
|
||||
### 8. angleInY вращает offset относительно глобального Option.HEADING
|
||||
- **Файл:** `JumpCalculator.java:105-113`
|
||||
- **Описание:** rotateAroundY использует разницу между текущим heading и Option.HEADING, а не между текущим и предыдущим heading. Блоки уходят вбок
|
||||
- **Фикс:** Вращать offset относительно предыдущего heading
|
||||
|
||||
### Правила (жёсткие)
|
||||
### 9. Дублирование canJump(Location) и canJump(Vector) — рассинхронизация
|
||||
- **Файл:** `JumpValidator.java:73-161`
|
||||
- **Описание:** Два метода canJump. Location-версия учитывает slab-корректировку, Vector — нет
|
||||
- **Фикс:** Убрать дублирование или добавить adjustment в Vector-версию
|
||||
|
||||
| Правило | Значение |
|
||||
|--------|----------|
|
||||
| **Целевой размер** | ~**150** строк на файл |
|
||||
| **Жёсткий потолок** | **190–200** строк (исключения только с комментом в PR) |
|
||||
| **Файлов в папке** | ≤ **6** (при росте — новая подпапка) |
|
||||
| **KISS** | Один публичный сценарий = один класс / use-case |
|
||||
| **DRY** | Сообщения только через `Locales`; команды — `Messages` / `admin.*` |
|
||||
| **SOLID** | `generator` не импортирует `adaptive`; связь только через `GeneratorEventListener` + DI в bootstrap |
|
||||
### 10. synchronizedList — итерации без синхронизации
|
||||
- **Файл:** `GeneratorState.java:35`
|
||||
- **Описание:** Итерации по `history` (cleanupDistantBlocks, historyContains, indexOfBlock) не обернуты в `synchronized`. Пока все на main thread, но это хрупко
|
||||
- **Фикс:** Добавить `synchronized(history)` на все итерации, либо убрать synchronizedList
|
||||
|
||||
### Целевая структура пакетов (✅ 2026-06-01)
|
||||
### 11. heading.clone() без null-проверки
|
||||
- **Файл:** `JumpCalculator.java:101-103`
|
||||
- **Описание:** `generator.state.heading.clone()` упадет с NPE, если heading не инициализирован до первого вызова calculateJumpOffset
|
||||
- **Фикс:** Добавить null-guard: если heading == null, инициализировать из Option.HEADING
|
||||
|
||||
```
|
||||
dev.loki.loparkour/
|
||||
├── LoParkour.java # main plugin class
|
||||
├── command/
|
||||
│ ├── LoParkourCommand.java
|
||||
│ ├── player/ # PlayerCommandHandler
|
||||
│ ├── admin/ # AdminCommandHandler
|
||||
│ └── schematic/ # SchematicCommandHandler
|
||||
├── config/
|
||||
│ ├── core/ # Config, ConfigLoader, ConfigAccessor, ConfigUpdater
|
||||
│ ├── locale/ # Locales, LocaleLoader, LocaleCache
|
||||
│ └── options/ # Option + OptionSQL, OptionGeneral, …
|
||||
├── generator/
|
||||
│ ├── core/ # ParkourGenerator, Profile, GeneratorState, …
|
||||
│ ├── jump/, lifecycle/, profile/, effect/
|
||||
│ └── core/GeneratorEventListener
|
||||
├── mode/
|
||||
│ ├── base/ # Mode, MultiMode, Modes, ModeMessages
|
||||
│ ├── impl/ # DefaultMode, SpeedrunMode, …
|
||||
│ └── elytra/ # ElytraGenerator + ring/physics/hud
|
||||
├── menu/
|
||||
│ ├── core/ # LPMenu, MainMenu, Menus, ParkourOption
|
||||
│ └── play/, settings/, community/, lobby/
|
||||
├── player/
|
||||
│ ├── core/ # ParkourUser, ParkourPlayer
|
||||
│ ├── service/ # UserRegistry, hotbar, scoreboard, …
|
||||
│ ├── spectator/
|
||||
│ └── data/
|
||||
├── util/
|
||||
│ ├── text/, item/, gui/, world/, particle/, misc/
|
||||
├── storage/
|
||||
│ ├── Storage.java # фасад
|
||||
│ ├── sql/ # StorageSQL, SQL*, миграции
|
||||
│ └── disk/ # StorageDisk (JSON)
|
||||
├── session/
|
||||
│ ├── core/Session.java
|
||||
│ └── manager/ # State, Player, User managers
|
||||
├── listener/
|
||||
│ ├── player/, gameplay/, schematic/
|
||||
├── leaderboard/
|
||||
│ ├── model/Score, core/, persistence/
|
||||
├── ghost/
|
||||
│ ├── model/, core/
|
||||
├── hook/
|
||||
│ ├── papi/, holo/, floodgate/, vault/
|
||||
├── schematic/
|
||||
│ ├── registry/Schematics.java
|
||||
│ └── lpschem/
|
||||
├── api/core/ + api/event/
|
||||
├── reward/core/, style/core/, world/core/
|
||||
├── adaptive/bootstrap/AdaptiveServices
|
||||
├── bootstrap/PluginBootstrap
|
||||
└── …
|
||||
```
|
||||
## MEDIUM
|
||||
|
||||
Скрипт переноса: `scripts/refactor-packages.ps1` (+ `fix-imports-after-refactor.ps1`).
|
||||
### 12. BlockSelector.selectBlockData рекурсия при пустом списке стилей
|
||||
- **Файл:** `BlockSelector.java:32-33`
|
||||
- **Описание:** Если style == null, код сбрасывает стиль на первый. `orElseThrow()` упадет, если список пуст
|
||||
- **Фикс:** Проверка `if (styles.isEmpty()) return Material.STONE.createBlockData()`
|
||||
|
||||
### Очередь разбиения (файлы > 150 строк)
|
||||
### 13. checkPlayerFall — break вместо continue
|
||||
- **Файл:** `LifecycleTickManager.java:74-78`
|
||||
- **Описание:** При падении двух игроков в одном тике, второй пропускает обработку до следующего тика
|
||||
- **Фикс:** Заменить break на continue
|
||||
|
||||
| Приоритет | Файл | Строк | Действие |
|
||||
|-----------|------|-------|----------|
|
||||
| ✅ 1 | `ElytraGenerator.java` | 101 | → `elytra/ElytraRingLoop`, `ElytraScoring`, `ElytraHud`, `ElytraPlayerKit`, `ElytraFireworkListener` |
|
||||
| ✅ 2 | `ParkourPlayer.java` | 166 | → `ParkourHotbar` |
|
||||
| 🟡 3 | `PAPIHook.java` | 194 | → placeholders по группам |
|
||||
| 🟡 4 | `LoParkour.java` | 192 | → `bootstrap/PluginBootstrap.java` |
|
||||
| 🟡 5 | `MetricsCollector.java` | 187 | → session hook + jump hook + flush |
|
||||
| 🟡 6 | `StorageSQL.java` | 179 | → queries / mapper / connection (уже частично) |
|
||||
| 🟡 7 | `BlockPlacer.java` | 176 | | |
|
||||
| 🟡 8 | `JumpValidator.java` | 172 | | |
|
||||
| 🟡 9 | `LoParkourCommand.java` | 170 | → делегаты только маршрутизация |
|
||||
| 🟡 10 | `CoopMode.java` | 165 | → `CoopGenerator` отдельный файл |
|
||||
| 🟢 | остальные 151–159 | 8 файлов | после топ-10 |
|
||||
### 14. removeTrailBlocks сдвигает индексы истории
|
||||
- **Файл:** `GeneratorCleanup.java:79-93`
|
||||
- **Описание:** `history.subList(startIndex, endIndex).clear()` сдвигает индексы при параллельном вызове historyContains
|
||||
- **Фикс:** Защититься копированием или проверять синхронизацию
|
||||
|
||||
### Чек-лист фазы 0
|
||||
### 15. Дублирование isSlabMaterial
|
||||
- **Файл:** `JumpValidator.java:67-71`
|
||||
- **Описание:** JumpValidator и BlockSelector имеют дублирующуюся логику определения slab. Рассинхронизация при изменениях
|
||||
- **Фикс:** Вынести в общий утилитарный метод
|
||||
|
||||
- [ ] Нет файлов > 200 строк (кроме явно согласованных)
|
||||
- [x] Пакеты разнесены (config/mode/command/generator/menu/player/util)
|
||||
- [ ] Нет циклических зависимостей `generator` ↔ `adaptive`
|
||||
- [ ] Единый `MessageService` / `Locales` для player + console + action bar + title
|
||||
- [ ] Удалить или подключить мёртвый код: `Menu`, `PagedMenu`, `DynamicMenu`, `MenuStub`
|
||||
- [ ] `Option.java` — только фасад, без роста (делегаты в `options/*`)
|
||||
- [x] `./gradlew build` зелёный
|
||||
- [ ] README/TODO не врут про «всё готово»
|
||||
### 16. DifficultyScore thresholds для льда (1.0/2.0) могут быть недостижимы
|
||||
- **Файл:** `GeneratorProfileManager.java:57-69`
|
||||
- **Описание:** packed_ice добавляется при difficulty >= 1.0, blue_ice при >= 2.0. Многие конфиги не достигают этих значений. Лед не появляется
|
||||
- **Фикс:** Нормализовать difficulty до предсказуемой шкалы или изменить thresholds
|
||||
|
||||
---
|
||||
### 17. schematicBlocks — ArrayList без thread safety
|
||||
- **Файл:** `GeneratorState.java:41`
|
||||
- **Описание:** schematicBlocks это plain ArrayList, без синхронизации, в отличие от history
|
||||
- **Фикс:** Добавить синхронизацию или документировать main-thread-only
|
||||
|
||||
## Баги и долг (найдено при аудите)
|
||||
## LOW
|
||||
|
||||
### 🔴 Сборка
|
||||
### 18. scoredBlocks HashSet не очищается при fall
|
||||
- **Файл:** `ParkourPlayer.java:125-127`
|
||||
- **Описание:** HashSet растет бесконечно. При возрождении на тех же координатах не засчитает блоки
|
||||
- **Фикс:** scoredBlocks.clear() при fall/reset
|
||||
|
||||
| # | Проблема | Статус |
|
||||
|---|----------|--------|
|
||||
| B1 | `SQLConnectionManager` visibility | ✅ |
|
||||
| B2 | `AdaptiveConfig` + `Config.CONFIG` | ✅ |
|
||||
### 19. getFormattedTime() — Date(elapsed) хак
|
||||
- **Файл:** `GeneratorStatistics.java:90-91`
|
||||
- **Описание:** `new Date(elapsed)` — Date ожидает timestamp от 1970, а передается длительность. Сломается при >24ч
|
||||
- **Фикс:** Использовать Duration API для форматирования
|
||||
|
||||
### 🟡 Локализация (i18n) — большинство закрыто
|
||||
|
||||
| # | Проблема | Статус |
|
||||
|---|----------|--------|
|
||||
| I1 | `LPMenu` back/next | ✅ `other.back`, `other.next` |
|
||||
| I2 | `other.no_menu_items` | ✅ |
|
||||
| I3 | joining disabled | ✅ `ModeMessages` + `other.joining_disabled` |
|
||||
| I4 | Elytra messages | ✅ `modes.elytra.*` |
|
||||
| I5–I11 | Race, coop, admin, schematic, commands, bungee, GUI stubs | ✅ (см. `locales/*.yml`) |
|
||||
| I12 | `ghost.saved` в en | ✅ |
|
||||
| I13 | `%[a-z]` vs `%s` | 🟢 документировать / унифицировать позже |
|
||||
| I14 | reload NPE с консоли | ✅ `getString(sender, …)` |
|
||||
| I15 | недостающие ключи | ✅ основной набор добавлен |
|
||||
|
||||
### 🟡 Логика / архитектура
|
||||
|
||||
| # | Проблема | Где |
|
||||
|---|----------|-----|
|
||||
| A1 | `MetricsCollector` на генераторе | ✅ `SessionStateManager` + `AdaptiveServices.attachToGenerator` |
|
||||
| A2 | Adaptive → профиль генерации | ✅ `GeneratorProfileManager` + `AdaptiveDifficulty` |
|
||||
| A3 | `adaptive.*` в config.yml | ✅ |
|
||||
| A4 | SQL таблица stats | ✅ `StorageSQL` + `SQLMigrationManager.createAdaptiveStatsTable` |
|
||||
| A5 | `DynamicMenu` мёртвый | 🔴 |
|
||||
| A6 | STONE debug-spam | ✅ только при отсутствии `.material` |
|
||||
| A7 | Несогласованные пути locale: `play.single.*` vs `modes.*` для режимов | `*Mode.getItem()` |
|
||||
| A8 | `sendHelp` упоминает `/LoParkour`, игроки используют `/parkour` | `PlayerCommandHandler` |
|
||||
|
||||
### 🟢 Мелочи
|
||||
|
||||
| # | Проблема |
|
||||
|---|----------|
|
||||
| M1 | `SpectatorMode`: MiniMessage `<red>` vs legacy `§c` в других режимах |
|
||||
| M2 | `HardcoreMode` / др.: смешение `ColorUtil` и сырого `sendMessage` |
|
||||
| M3 | Unit/integration тесты в TODO помечены ✅ — **фактически** только 2 test-класса |
|
||||
|
||||
---
|
||||
|
||||
## Фаза 1: Рефакторинг структуры (статус реальный)
|
||||
|
||||
### Архитектура
|
||||
|
||||
| # | Задача | Статус |
|
||||
|---|--------|--------|
|
||||
| 1 | Разбить `ParkourGenerator` | 🟡 Частично (composition), файл 159 строк — ок |
|
||||
| 2 | `GeneratorProfileManager` | 🟡 Есть, без отдельного `ProfileCalculator` |
|
||||
| 3 | statistics из generator | 🟡 `GeneratorStatistics` внутри generator, не пакет `statistics/` |
|
||||
| 4 | Структура пакетов `generator/core`, `jump`, `lifecycle` | 🟡 Частично |
|
||||
| 5 | `GeneratorEventListener` | ✅ Интерфейс + вызовы в `PlayerInteractionHandler` |
|
||||
|
||||
### Большие файлы (цель < 200) — **не выполнено**
|
||||
|
||||
| Файл | Строк | Статус |
|
||||
|------|-------|--------|
|
||||
| ElytraGenerator.java | 101 | ✅ |
|
||||
| ParkourPlayer.java | 166 | ✅ |
|
||||
| PAPIHook, LoParkour, MetricsCollector, … | 151–194 | 🟡 см. Фаза 0 |
|
||||
|
||||
---
|
||||
|
||||
## Фаза 2: Адаптивная система (статус реальный)
|
||||
|
||||
| # | Задача | Статус |
|
||||
|---|--------|--------|
|
||||
| 1 | `adaptive/model/` | 🟡 Классы есть |
|
||||
| 2 | `adaptive/storage/` | ✅ compile + SQL/file repo |
|
||||
| 3 | SQL таблицы | ✅ CREATE в `StorageSQL` / `SQLMigrationManager` |
|
||||
| 4 | File JSON storage | ✅ |
|
||||
| 5 | `MetricsCollector` | ✅ |
|
||||
| 6 | Calculator / Adjuster в профиле | ✅ через `AdaptiveDifficulty` |
|
||||
| 7 | `AdaptiveMode`, `StatsService` | 🔴 отдельный режим не нужен пока |
|
||||
| 8 | `config.yml` → `adaptive.*` | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## Фаза 3: ElytraMode (статус реальный)
|
||||
|
||||
| # | Задача | Статус |
|
||||
|---|--------|--------|
|
||||
| 1 | `ElytraMode` | 🟡 Есть |
|
||||
| 2 | `ElytraGenerator` + helpers | ✅ Разбит на `mode/elytra/*` |
|
||||
| 3 | `ElytraConfig`, `RingRenderer`, … | ✅ В пакете `mode/elytra/` |
|
||||
| 4 | `modes/elytra.yml` | 🔴 Нет |
|
||||
| 5 | i18n для elytra | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## Фаза 4: Режимы (статус реальный)
|
||||
|
||||
| Режим | Меню | i18n join disabled | Примечание |
|
||||
|-------|------|-------------------|------------|
|
||||
| default, speedrun, hardcore, gravity-shift | ✅ SingleMenu | 🔴 хардкод EN | |
|
||||
| elytra, race | ✅ | 🔴 | race — ещё UI хардкод |
|
||||
| coop | ❌ (null item — ок) | 🔴 | title хардкод |
|
||||
| spectator | ✅ | 🟡 частично Locales | |
|
||||
| roguelike | — | — | **Нет в коде** (в старом TODO было ✅ — ошибка) |
|
||||
|
||||
---
|
||||
|
||||
## Фаза 5: i18n — единая система сообщений
|
||||
|
||||
| # | Задача | Статус |
|
||||
|---|--------|--------|
|
||||
| 1 | `other.back` / `other.next` | ✅ |
|
||||
| 2 | Ключи из таблицы I* | ✅ |
|
||||
| 3 | `MessageKeys` enum или constants class (DRY путей) | 🟢 |
|
||||
| 4 | `Locales.getString(CommandSender)` — использовать везде для консоли | 🟡 Частично |
|
||||
| 5 | Убрать debug-spam STONE | ✅ |
|
||||
| 6 | Синхронизировать `en.yml` ↔ `ru.yml` | 🟡 периодический diff |
|
||||
| 7 | Проверка CI: скрипт diff ключей locale vs `Locales.getString`/`sendTranslated` | 🟢 |
|
||||
|
||||
---
|
||||
|
||||
## Технический долг (актуальный)
|
||||
|
||||
| # | Задача | Приоритет |
|
||||
|---|--------|-----------|
|
||||
| 1 | Compile adaptive | ✅ |
|
||||
| 2 | Bootstrap MetricsCollector | ✅ |
|
||||
| 3 | SQL adaptive table | ✅ |
|
||||
| 4 | Удалить или реализовать `Menu`/`PagedMenu`/`DynamicMenu` | 🟡 |
|
||||
| 5 | Реальные тесты adaptive + modes | 🟢 |
|
||||
|
||||
---
|
||||
|
||||
## Чек-лист перед релизом (честный)
|
||||
|
||||
- [x] `./gradlew build` без ошибок
|
||||
- [x] Нет хардкода RU в `LPMenu` / основных режимах и админке
|
||||
- [ ] Полный diff ключей locale vs Java (CI)
|
||||
- [ ] Адаптивность: ручной тест на сервере (`adaptive.enabled: true`)
|
||||
- [ ] ElytraMode протестирован вручную
|
||||
- [ ] Нет файлов > 200 строк (или исключения задокументированы)
|
||||
- [ ] README / CHANGELOG соответствуют коду
|
||||
|
||||
---
|
||||
|
||||
## SQL миграция (без изменений)
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS loparkour_player_stats (
|
||||
player_uuid VARCHAR(36) PRIMARY KEY,
|
||||
skill_rating DOUBLE DEFAULT 1.0,
|
||||
sessions_count INT DEFAULT 0,
|
||||
total_jumps INT DEFAULT 0,
|
||||
total_falls INT DEFAULT 0,
|
||||
longest_streak INT DEFAULT 0,
|
||||
last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
INDEX idx_skill_rating (skill_rating),
|
||||
INDEX idx_last_updated (last_updated)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Оценка времени (пересмотр)
|
||||
|
||||
| Фаза | Дней | Комментарий |
|
||||
|------|------|-------------|
|
||||
| **0: KISS/DRY/SOLID + лимит строк** | 5–8 | Блокирует нормальную поддержку |
|
||||
| **5: i18n** | 2–3 | Можно параллельно с 0 |
|
||||
| **2: Adaptive** | 3–4 | После зелёной сборки + bootstrap |
|
||||
| **3: Elytra split** | 1–2 | После 0 |
|
||||
| **4: Режимы / тесты** | 2–3 | |
|
||||
| **Итого** | **13–20** | Старые «8–12 дней» занижены |
|
||||
|
||||
---
|
||||
|
||||
## Метрики успеха (пересмотр)
|
||||
|
||||
### Технические
|
||||
- [ ] ≥ 90% файлов ≤ 150 строк
|
||||
- [ ] 0 файлов > 200 строк
|
||||
- [ ] SOLID: `adaptive` → listener/port, не прямой import generator internals
|
||||
- [ ] `./gradlew test` — осмысленное покрытие, не «галочка в TODO»
|
||||
|
||||
### Функциональные
|
||||
- [ ] 2+ языка без хардкода в Java (кроме логов debug)
|
||||
- [ ] Adaptive влияет на профиль после N сессий
|
||||
- [ ] ElytraMode без монолита 250+ строк
|
||||
|
||||
### Производительность
|
||||
- [ ] TPS стабилен 50+ игроков (профилирование)
|
||||
- [ ] SQL batch / flush adaptive < 50ms
|
||||
### 20. isUnsafeParkourMaterial блокирует PUMPKIN и _BED без причины
|
||||
- **Файл:** `BlockSelector.java:48-60`
|
||||
- **Описание:** PUMPKIN не имеет onPlace-сканирования соседей. _BED безопасны при setBlockData с флагом false
|
||||
- **Фикс:** Убрать лишние исключения
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import dev.loki.loparkour.mode.impl.CoopMode;
|
|||
import dev.loki.loparkour.mode.impl.DefaultMode;
|
||||
import dev.loki.loparkour.mode.impl.ElytraMode;
|
||||
import dev.loki.loparkour.mode.impl.GravityShiftMode;
|
||||
import dev.loki.loparkour.mode.impl.HardcoreMode;
|
||||
import dev.loki.loparkour.mode.impl.RaceMode;
|
||||
import dev.loki.loparkour.mode.impl.SpectatorMode;
|
||||
import dev.loki.loparkour.mode.impl.SpeedrunMode;
|
||||
|
|
@ -76,7 +75,6 @@ public final class PluginBootstrap {
|
|||
Registry.register(new RaceMode());
|
||||
Registry.register(new CoopMode());
|
||||
Registry.register(new GravityShiftMode());
|
||||
Registry.register(new HardcoreMode());
|
||||
Modes.init();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ public class Modes {
|
|||
public static RaceMode RACE;
|
||||
public static CoopMode COOP;
|
||||
public static GravityShiftMode GRAVITY_SHIFT;
|
||||
public static HardcoreMode HARDCORE;
|
||||
|
||||
public static void init() {
|
||||
DEFAULT = (DefaultMode) Registry.getMode("default");
|
||||
|
|
@ -22,6 +21,5 @@ public class Modes {
|
|||
RACE = (RaceMode) Registry.getMode("race");
|
||||
COOP = (CoopMode) Registry.getMode("coop");
|
||||
GRAVITY_SHIFT = (GravityShiftMode) Registry.getMode("gravity-shift");
|
||||
HARDCORE = (HardcoreMode) Registry.getMode("hardcore");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,85 +0,0 @@
|
|||
package dev.loki.loparkour.mode.impl;
|
||||
import dev.loki.loparkour.mode.base.ModeMessages;
|
||||
import dev.loki.loparkour.mode.base.Mode;
|
||||
import dev.loki.loparkour.mode.base.Modes;
|
||||
|
||||
import dev.loki.loparkour.config.core.Config;
|
||||
import dev.loki.loparkour.config.locale.Locales;
|
||||
import dev.loki.loparkour.generator.core.coordinator.ParkourGenerator;
|
||||
import dev.loki.loparkour.leaderboard.core.Leaderboard;
|
||||
import dev.loki.loparkour.player.core.ParkourPlayer;
|
||||
import dev.loki.loparkour.session.core.Session;
|
||||
import dev.loki.loparkour.util.item.Item;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Hardcore Mode — lose all collected rewards on fall.
|
||||
*/
|
||||
public class HardcoreMode implements Mode {
|
||||
|
||||
private final Leaderboard leaderboard = new Leaderboard(getName(), Leaderboard.Sort.SCORE);
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getName() {
|
||||
return "hardcore";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Item getItem(String locale) {
|
||||
return Locales.getItem(locale, "play.single.hardcore");
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Leaderboard getLeaderboard() {
|
||||
return leaderboard;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void create(Player player) {
|
||||
if (!ModeMessages.checkJoiningEnabled(player)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ParkourPlayer pp = ParkourPlayer.getPlayer(player);
|
||||
if (pp != null && pp.session.generator instanceof HardcoreGenerator) {
|
||||
return;
|
||||
}
|
||||
|
||||
player.closeInventory();
|
||||
Session.create(session -> new HardcoreGenerator(session), null, null, player);
|
||||
}
|
||||
|
||||
// ── HardcoreGenerator ─────────────────────────────────────────────────────
|
||||
|
||||
private static class HardcoreGenerator extends ParkourGenerator {
|
||||
|
||||
private final boolean resetRewards;
|
||||
|
||||
public HardcoreGenerator(@NotNull Session session) {
|
||||
super(session);
|
||||
this.resetRewards = Config.CONFIG.isPath("modes.hardcore.reset-rewards")
|
||||
? Config.CONFIG.getBoolean("modes.hardcore.reset-rewards") : true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fall() {
|
||||
if (resetRewards) {
|
||||
for (ParkourPlayer pp : getPlayers()) {
|
||||
pp.collectedRewards.clear();
|
||||
}
|
||||
}
|
||||
|
||||
super.fall();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mode getMode() {
|
||||
return Modes.HARDCORE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -208,11 +208,8 @@ modes:
|
|||
amplifier: 1
|
||||
duration: 5
|
||||
|
||||
hardcore:
|
||||
reset-rewards: true
|
||||
|
||||
elytra:
|
||||
enabled: true
|
||||
enabled: false
|
||||
ring-distance-min: 20
|
||||
ring-distance-max: 40
|
||||
ring-size: 5
|
||||
|
|
|
|||
|
|
@ -40,11 +40,6 @@ play:
|
|||
name: "�D4FF&lGravity Shift"
|
||||
lore: "񢩈Random potion effects||񢩈every few jumps"
|
||||
|
||||
hardcore:
|
||||
material: "skeleton_skull"
|
||||
name: "�D4FF&lHardcore"
|
||||
lore: "񢩈Lose all rewards on fall||񢩈No second chances."
|
||||
|
||||
spectator:
|
||||
name: "&#FFFFFFSpectator"
|
||||
|
||||
|
|
|
|||
|
|
@ -39,11 +39,6 @@ play:
|
|||
name: "�D4FF&lГравитационный сдвиг"
|
||||
lore: "񢩈Случайные эффекты зелий||񢩈каждые несколько прыжков"
|
||||
|
||||
hardcore:
|
||||
material: "skeleton_skull"
|
||||
name: "�D4FF&lХардкор"
|
||||
lore: "񢩈Потеря всех наград при падении||񢩈Второго шанса не будет."
|
||||
|
||||
spectator:
|
||||
name: "&#FFFFFFНаблюдатель"
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue