- Delete schematic/legacy/lpschem/ (LPSchematicLegacy + 5 pojo Gson models) - Delete schematic/convert/LpschemConverter and /lp schematic convert command - Remove SchematicClipboardBuilder.fromLegacy, convert branches in dispatcher/ tab-completer/help, locales (ru/en), SCHEMATICS.md and yml comments - Kept only .schem (WorldEdit) + .nbt - Build green: checkstyle 0 errors, PMD 33
265 lines
30 KiB
Markdown
265 lines
30 KiB
Markdown
# Целевая архитектура LoParkour
|
||
|
||
> Руководство для последующих рефакторингов god-классов и добавления новых режимов (Duels, Elytra).
|
||
> Документ описывает **целевое** состояние; текущее зафиксировано в разделе «Текущее состояние».
|
||
|
||
---
|
||
|
||
## Принципы проектирования
|
||
|
||
1. **KISS** — минимум слоёв и абстракций для решения задачи; каждый класс делает одну вещь.
|
||
2. **DRY** — одна реализация фичи в одном месте; дублирование выносится в общий переиспользуемый слой
|
||
(напр. рендеринг/размещение блоков — в `block/`).
|
||
3. **SOLID**:
|
||
- **SRP** — у класса ровно одна ответственность (главный драйвер декомпозиции god-классов);
|
||
- **OCP** — новые режимы добавляются новыми подклассами/реализациями, без правки существующих;
|
||
- **ISP** — узкие интерфейсы (`Mode`, `MultiMode`, `GeneratorEventListener`);
|
||
- **DIP** — домен зависит от абстракций, инфраструктура зависит от домена.
|
||
4. **Композиция вместо наследования** — god-классы разбиваются на коллабораторов, инжектируемых в тонкий
|
||
фасад (паттерн уже применён в `ParkourGenerator`, `BlockPlacer`, `StorageSQL`, `Leaderboard` — развиваем его).
|
||
5. **Границы размера** (жёсткие, зафиксированы в `config/checkstyle/checkstyle.xml`, severity=`error`):
|
||
- файл ≤ **200 строк** (`FileLength`), метод ≤ **80 строк** (`MethodLength`),
|
||
параметров ≤ **7** (`ParameterNumber`), линия ≤ **150 символов** (`LineLength`),
|
||
`NPathComplexity` ≤ **50**, цикломатическая сложность ≤ **20**, вложенность if/for/try ≤ 3/2/2.
|
||
6. **Явный порядок зависимостей** — слои смотрят «вниз», циклы запрещены:
|
||
|
||
`api/` → `core/` (домен) → `block/` + subsystems (`generator/`, `schematic/`, `adaptive/`, `ghost/`,
|
||
`style/`, `world/`, `leaderboard/`, `reward/`, `duels/`, `elytra/`) → платформа
|
||
(`storage/`, `config/`, `hook/`, `listener/`, `command/`, `menu/`, `util/`).
|
||
|
||
7. **Новые файлы обязаны соответствовать всем правилам** — их нельзя добавлять в `suppressions.xml`.
|
||
Супрессии — только временный долг существующих legacy-файлов, удаляются по мере рефакторинга.
|
||
8. **Одна папка = одна цель.** Каждый пакет содержит файлы ОДНОЙ ответственности (пример нормы:
|
||
`command/schematic/` — все 6 файлов про схематик-команды). Если в папку попадают разные по смыслу
|
||
классы — режем на подпапки по под-целям (пример: `util/gui` — это часть подсистемы меню, а не общий
|
||
util; `mode/impl/` — соли-режимы, мульти-режимы и барьеры — разные цели). Ориентир: ~3-5 файлов на
|
||
папку; папка из 1 файла допустима, если это самостоятельная единица (util, model, msg), но НЕ ради
|
||
искусственной нарезки.
|
||
|
||
## Текущее состояние (кратко)
|
||
|
||
- **173 Java-файла** в `src/main/java/dev/loki/loparkour/` (база пакета `dev.loki.loparkour`), Java 21,
|
||
Paper 1.20.4 / Spigot API + Adventure, сборка shadow + checkstyle + PMD.
|
||
- Декомпозиция по паттерну «фасад + коллабораторы» уже частично проведена (`ParkourGenerator`,
|
||
`BlockPlacer`, `StorageSQL`, `Leaderboard`, `SessionStateManager`), но фасады и часть режимов всё ещё
|
||
превышают лимит 200 строк.
|
||
|
||
**God-классы (>200 строк) и где они «протекают»:**
|
||
|
||
| Класс | Строк | Протекающие обязанности |
|
||
|---|---|---|
|
||
| `command/LoParkourCommand` | 302 | роутинг по аргументам (0..4) + join/leaderboard логика + весь tab-complete |
|
||
| `command/schematic/SchematicCommandHandler` | 274 | сабкоманды схем + per-player кэш `SELECTIONS` |
|
||
| `config/core/ConfigUpdater` | 229 | шаблон-merge, YAML-парсинг full-path, бэкап/восстановление |
|
||
| `generator/jump/placement/BlockPlacer` | 227 | выбор BlockData, расчёт прыжка, запись блоков, эффекты, паста схем |
|
||
| `mode/impl/InvisibleBarrierMode` | 222 | `Mode`-фасад + `BarrierGenerator` + отрисовка контуров частицами |
|
||
| `hook/papi/PAPIHook` | 220 | метаданные expansion + роутинг + форматтеры рангов/сложности |
|
||
| `command/admin/AdminCommandHandler` | 214 | forcejoin/forceleave/reset/recoverinventory + резолв игроков |
|
||
| `adaptive/core/MetricsCollector` | 214 | метрики, near-miss-анализ, автосейв, кэш игроков |
|
||
| `storage/sql/StorageSQL` | 209 | инициализация пула, DDL, read/write скоров, read/write игрока |
|
||
| `player/core/ParkourPlayer` | 207 | Expose-настройки, скоринг-сет, статические реестры, сохранение, setup/hotbar |
|
||
| `leaderboard/core/Leaderboard` | 203 | CRUD, сортировка, тай-брейк, снапшоты рангов |
|
||
| `mode/impl/CoopMode` | 200 | на границе: `MultiMode` + `CoopGenerator` (contributions/милстоуны) |
|
||
|
||
Сопутствующие риски из `TODO.md`, влияющие на декомпозицию: статические mutable-поля
|
||
(`GeneratorState` — 15 публичных полей, `ParkourGenerator.session/island/profile/state/generatorOptions` —
|
||
п.107), рекурсия `unregister`/Coop (п.24), утечка SQL-пулов (п.27), per-player GUI-состояние (п.78),
|
||
неатомарный reload (п.70).
|
||
|
||
## Целевая структура пакетов
|
||
|
||
Новые режимы выносятся в **отдельные корневые пакеты** `duels/` и `elytra/` (не в существующий `mode/impl/`).
|
||
Общий переиспользуемый рендеринг/размещение блоков — в **`block/`**. Ядро домена — в **`core/`**.
|
||
Существующие пакеты сохраняются и встраиваются в слои.
|
||
|
||
```text
|
||
dev.loki.loparkour
|
||
│
|
||
├── api/ # Публичный контракт для внешних плагинов (единственный «экспортный» слой)
|
||
│ ├── core/ # ParkourAPI, Registry — регистрация режимов/стилей/событий
|
||
│ └── event/ # Доменные события (join/leave/score/fall/schematic/spectate/block-generate)
|
||
│
|
||
├── core/ # ЯДРО ДОМЕНА: агрегаты и доменные сервисы (слой ниже api/)
|
||
│ ├── model/ # Неизменяемые доменные модели (Score, Reward, GhostData и т.п.)
|
||
│ ├── player/ # Агрегат игрока: ParkourPlayer, ParkourUser, ParkourSpectator
|
||
│ ├── session/ # Session + жизненный цикл сессии (create/join/leave/removePlayers)
|
||
│ ├── mode/ # Контракты режимов: Mode, MultiMode, Modes
|
||
│ └── service/ # Доменные сервисы (UserRegistry, ParkourHotbar, ScoreboardManager)
|
||
│
|
||
├── block/ # ОБЩИЙ слой размещения/рендеринга блоков (для ВСЕХ генераторов и режимов)
|
||
│ ├── engine/ # BlockWriter: запись BlockData в мир + BlockEffectPlayer (particles/sound)
|
||
│ ├── selection/ # BlockSelector: выбор BlockData по стилям/весам (хук selectBlockData для режимов)
|
||
│ ├── style/ # BlockStyleProvider: материал/цвет/сдвиг блока по стилю
|
||
│ ├── jump/ # JumpCalculator, JumpValidator, JumpDirector, JumpOffsetGenerator, JumpType
|
||
│ ├── schematic/ # Паста схематик в мир (SchemPaster/StructurePaster, событие генерации)
|
||
│ ├── render/ # Частице-рендер: BlockOutlineRenderer, контуры/боксы (из InvisibleBarrierMode)
|
||
│ └── client/ # Клиент-сайд виртуальные блоки (без записи в мир) — использует Elytra
|
||
│
|
||
├── generator/ # SUBSYSTEM «бесконечная генерация» (сохраняется, использует block/)
|
||
│ ├── core/coordinator/ # ParkourGenerator (фасад), GeneratorProfileManager, GeneratorStatistics
|
||
│ ├── core/model/ # GeneratorState, Profile, Island, GeneratorOption
|
||
│ ├── jump/calculation/ # Расчёт прыжков (мигрирует в block/jump после стабилизации API)
|
||
│ ├── jump/placement/ # Оркестрация генерации блока (DEFAULT/SPECIAL/SCHEMATIC)
|
||
│ ├── lifecycle/loop/ # Тик/фолл/ресет/события генератора
|
||
│ ├── lifecycle/player/ # PlayerInteractionHandler, GeneratorCleanup
|
||
│ └── profile/ # GenerationProfileLoader (профили сложности)
|
||
│
|
||
├── schematic/ # SUBSYSTEM «схематики»: core/create/convert/nbt/schem/registry/legacy
|
||
├── adaptive/ # SUBSYSTEM «адаптивная сложность»: core, model, storage, bootstrap
|
||
├── ghost/ # SUBSYSTEM «ghost-реплей»: core (GhostManager/Recorder/Player), model
|
||
├── style/ # Визуальные темы: Style, RandomStyle
|
||
├── world/ # Зоны/острова: Divider, World
|
||
├── leaderboard/ # Лидерборды: core (Leaderboard/Sorter), model (Score), persistence
|
||
├── reward/ # Награды: Reward, Rewards
|
||
│
|
||
├── duels/ # НОВЫЙ РЕЖИМ «Duels» (корневой пакет, генератор на игрока)
|
||
│ ├── mode/ # DuelsMode implements MultiMode (create/join/leave, isAcceptingPlayers)
|
||
│ ├── generator/ # DuelsGenerator (owner) + SingleDuelsGenerator (генератор на игрока)
|
||
│ ├── match/ # goal, счёт, спавн-площадки, цвета игроков, initCountdown/win
|
||
│ └── menu/ # MultiplayerMenu / lobby invite (фильтр instanceof MultiMode)
|
||
│
|
||
├── elytra/ # НОВЫЙ РЕЖИМ «Elytra» (корневой пакет, клиент-сайд труба)
|
||
│ ├── mode/ # ElytraMode + вариации (Default/SpeedDemon/MinSpeed/TimeTrial/Close/Obstacle)
|
||
│ ├── section/ # ElytraSection, KnotDirector (сплайн/кольца), PointType
|
||
│ ├── render/ # Клиент-сайд рендер колец (sendBlockChanges)
|
||
│ └── generator/ # ElytraGenerator (свой tick()/fall-детект), фейерверк-буст
|
||
│
|
||
├── player/ # Существующий пакет (3-й слой, обслуживает core/player)
|
||
│ ├── core/ # ParkourPlayer, ParkourUser (агрегаты)
|
||
│ ├── data/ # PreviousData, InventoryData (save/restore состояния)
|
||
│ ├── service/ # ParkourHotbar, UserRegistry, ScoreboardManager, PlayerSettingsManager, BungeeUtil
|
||
│ └── spectator/ # ParkourSpectator
|
||
├── session/ # Session + Session*Manager (State/Player/User; тик пер-сессии)
|
||
├── mode/ # Существующие режимы: base/ (контракты), impl/solo|multi|barrier/
|
||
├── storage/ # Платформа хранения: Storage, disk/StorageDisk, sql/StorageSQL + SQL*
|
||
├── config/ # Платформа конфигурации: core, locale, options
|
||
├── hook/ # Платформенные интеграции: papi/ (+ resolver/), vault/, holo/, floodgate/
|
||
├── listener/ # Bukkit-листенеры: gameplay/, player/, schematic/
|
||
├── command/ # Команды: core/ (LoParkourCommand+Router+TabCompleter), util/, admin/, player/, schematic/
|
||
├── menu/ # GUI: core/, community/, lobby/, play/, settings/
|
||
└── util/ # Общие утилиты: gui/, item/, misc/, particle/, text/, world/
|
||
```
|
||
|
||
### Текущее состояние (после реструктуризации)
|
||
|
||
Правило «одна папка = одна цель» применено к переполненным пакетам:
|
||
|
||
- **`command/`** — `core/` (LoParkourCommand, CommandRouter, CommandTabCompleter — вход/роутинг/табы),
|
||
`util/` (CommandUtil), `player/` (PlayerCommandHandler, Join, Leaderboard), `admin/` (`ops/` — Force*/Reset/
|
||
RecoverInventory, `support/` — TargetResolver, Responses), `schematic/` (одна цель — схематик-команды).
|
||
- **`mode/impl/`** — `solo/` (DefaultMode, SpeedrunMode, GravityShiftMode), `multi/` (CoopMode, RaceMode,
|
||
SpectatorMode), `barrier/` (InvisibleBarrierMode, BarrierGenerator, BarrierRenderer).
|
||
- **`hook/papi/`** — `PAPIHook`+`PlaceholderDispatcher` (вход/диспатч), `resolver/` (Global/Player/ScoreRank
|
||
резолверы + PlaceholderFormatters).
|
||
- **`storage/sql/`** — корень (StorageSQL, SQLConnectionManager, SQLDataMapper), `schema/` (SQLSchemaManager,
|
||
SQLMigrationManager), `query/` (SQLQueryBuilder, SQLQueryExecutor), `repo/` (SQLPlayerRepository,
|
||
SQLScoreRepository).
|
||
- **`adaptive/core/`** — корень (AdaptiveDifficulty, MetricsCollector), `calc/` (DifficultyCalculator/
|
||
Adjuster/SkillAnalyzer), `metrics/` (MetricEventCollector, MetricsCacheStore, JumpTimingTracker), `model/`
|
||
(DifficultyWeights).
|
||
- **`api/event/`** — `session/` (Join/Leave/Spectate), `score/` (Score/Fall), `generation/` (Block/Schematic).
|
||
- **`config/core/`** — корень (Config, ConfigLoader, ConfigAccessor), `merge/` (ConfigUpdater,
|
||
ConfigMergeParser, ConfigMergeProcessor).
|
||
- **`config/options/`** — корень (Option, OptionGeneral), `section/` (Generation/Particles/SQL/Styles).
|
||
- **`player/service/`** — корень (UserRegistry, PlayerSettingsManager, BungeeUtil), `ui/` (ParkourHotbar,
|
||
ScoreboardManager).
|
||
- **`menu/core/`** — корень (LPMenu, Menus, MenuStub), `screen/` (MainMenu, DynamicMenu, ParkourOption);
|
||
`util/gui/` переехали в **`menu/gui/`** (GUI migration-стабы).
|
||
- **`schematic/legacy/lpschem/`** — ~~`LPSchematicLegacy` + `pojo/`~~ **удалено** (п.117 TODO): формат `.lpschem` выпилен, остались `.schem` (WorldEdit) + `.nbt`.
|
||
|
||
Пересекающие пакеты классы стали `public` (сигнатуры методов не менялись, только видимость). Оставшиеся
|
||
папки с 4–6 файлами — цельные по смыслу (jump-расчёты, block-placement, player-core, pojo и т.п.) — не дробятся.
|
||
|
||
## Карта миграции
|
||
|
||
Таблица «старый класс → решение» для god-классов. `split` — разделить на новых коллабораторов,
|
||
`keep` — оставить как есть, `move` — перенести ответственность в другой пакет без изменения поведения.
|
||
|
||
| Старый класс | Решение | Новые классы (пакет · ответственность) |
|
||
|---|---|---|
|
||
| `command/LoParkourCommand` | **split** | `command/CommandRouter` (диспетчер args.length 0..4) · `command/CommandContext` (sender/player/args-обёртка, сокращает параметры) · `command/JoinCommand` (join: режим/игрок/сессия) · `command/LeaderboardCommand` · `command/completion/CommandCompleter` (весь onTabComplete) · `command/completion/SchematicCompleter` + `ModeCompleter` (подкомандные) · `player/PlayerCommandHandler`, `admin/AdminCommandHandler`, `schematic/SchematicCommandHandler` — **keep** (уже выделены) |
|
||
| `command/schematic/SchematicCommandHandler` | **split** | `command/schematic/SchematicCommandRouter` (роутинг) · `.../SchematicCreateHandler` · `.../SchematicPasteHandler` · `.../SchematicWandSession` (per-player `SELECTIONS`, cleanup на quit — чинит п.84) · `.../SchematicConvertHandler` |
|
||
| `command/admin/AdminCommandHandler` | **split** | `command/admin/ForceJoinHandler` · `.../ForceLeaveHandler` · `.../ResetHandler` · `.../RecoverInventoryHandler` (по сабкоманде) · `command/admin/PlayerResolver` (resolveUUID/resolvePlayerName/findNearest; поддержка `BlockCommandSender` — п.119) |
|
||
| `config/core/ConfigUpdater` | **split** | `config/core/ConfigTemplate` (чтение шаблона) · `config/core/YamlPathParser` (full-path-парсинг, из `extractFullPathValues`) · `config/core/ConfigMerger` (точечный merge отсутствующих ключей, сохранение листов/комментариев — фикс п.26) · `config/core/ConfigBackup` (бэкап/восстановление) · `ConfigUpdater` = тонкий фасад (keep) |
|
||
| `generator/jump/placement/BlockPlacer` | **split+move** | `block/engine/BlockWriter` (placeBlockData, setBlockData+phys) · `block/engine/BlockEffectPlayer` (particles/sound на всех игроков) · `block/selection/BlockSelector` (**move**, уже есть) · `block/jump/JumpCalculator` (**move**, уже есть) · `generator/jump/placement/BlockGenerationOrchestrator` (generateSingleBlock, switch DEFAULT/SPECIAL/SCHEMATIC) · `generator/jump/placement/SchematicBlockGenerator` (tryGenerateSchematic) · `block/jump/TallMaterialResolver` (isTallMaterial + форм.высота) |
|
||
| `mode/impl/InvisibleBarrierMode` | **split+move** | `mode/impl/InvisibleBarrierMode` (keep, тонкий фасад) · `mode/impl/barrier/BarrierGenerator` (keep как есть, переезжает в подпакет) · `mode/impl/barrier/BarrierVisualState` (activeBarriers + blockColors + self-clean) · `block/render/BlockOutlineRenderer` (drawEdge/drawBlockOutline) · `block/render/ParticleBoxGeometry` (record Point + рёбра) |
|
||
| `hook/papi/PAPIHook` | **split** | `hook/papi/PAPIHook` (keep, тонкий: метаданные expansion + роутинг) · `hook/papi/PlaceholderRegistry` (плейсхолдер → resolver) · `hook/papi/PlayerPlaceholders` (score/time/lead/style/difficulty…) · `hook/papi/LeaderboardPlaceholders` (rank_*, leader/record) · `hook/papi/DifficultyFormatter` (parseDifficulty) · опционально `ScoreUntilPlaceholder` (score_until_N) · переименовать id `witp` → `loparkour` (п.118) |
|
||
| `storage/sql/StorageSQL` | **split** | `storage/sql/StorageSQL` (keep, фасад) · `storage/sql/SQLPool` (один Hikari-пул на плагин — фикс п.27, `setInitializationFailTimeout(-1)` п.28) · `storage/sql/SQLSchemaManager` (единый мигратор DDL — фикс п.62) · `storage/sql/SQLScoreRepository` (readScores/writeScores) · `storage/sql/SQLPlayerRepository` (readPlayer/writePlayer) · `storage/sql/SQLCallbackQueue` (runWhenConnected, фикс п.29) · `SQLQueryBuilder`, `SQLDataMapper`, `SQLMigrationManager` — keep |
|
||
| `player/core/ParkourPlayer` | **split** | `core/player/ParkourPlayer` (*keep* агрегат, тонкий) · `player/data/PlayerSettings` (Expose-поля настроек + их serialize/restore) · `player/service/ScoreTracker` (scoredBlocks/hasScored/markScored/blockKey) · `player/service/PlayerPersistence` (save/read) · `player/service/PlayerSetup` (setup/teleport/hotbar) · `player/service/GeneratorSettingsSync` (updateGeneratorSettings) · `player/core/ParkourPlayerRegistry` (статические getPlayer/getPlayers/isPlayer — заодно фикс O(n), п.47) |
|
||
| `leaderboard/core/Leaderboard` | **split** | `leaderboard/core/Leaderboard` (keep, фасад) · `leaderboard/core/ScoreComparator` (isBetterThan/тай-брейк) · `leaderboard/core/LeaderboardQueries` (getRank/getScoreAtRank/sort-снапшоты) · `LeaderboardStorage`, `LeaderboardSorter` — keep · **не split**: `model/Score.getTimeMillis` — bugfix парсинга `mm:ss.SSS`/`HH:mm:ss.SSS` и `long` (п.25) |
|
||
| `adaptive/core/MetricsCollector` | **split** | `adaptive/core/MetricsCollector` (keep: сценарии GeneratorEventListener) · `adaptive/core/JumpAnalyzer` (near-miss, время на блок) · `adaptive/core/MetricsAutoSave` (таймер автосейва, фикс Folia п.21) · `adaptive/core/PlayerMetricsCache` (кэш/выгрузка) · `adaptive/storage/StatsRepository` — keep |
|
||
| `mode/impl/CoopMode` | **split** (на границе 200) | `mode/impl/coop/CoopMode` (keep: MultiMode-фасад) · `mode/impl/coop/CoopSession` (создание лобби/инвайтов, создание сессии в create — фикс п.23) · `mode/impl/coop/CoopGenerator` (contributions/милстоны) · guard в unregister (фикс рекурсии п.24) |
|
||
|
||
## Новые режимы
|
||
|
||
Оба режима разрабатываются сразу в чистом виде (полное соответствие checkstyle, ноль PMD-нарушений) —
|
||
новая функциональность не может «перевозить» legacy-долг. Код режима НЕ добавляется в `suppressions.xml`.
|
||
|
||
### Duels — `duels/*` (в терминах задачи `mode/duels/*`)
|
||
|
||
Модель «генератор на игрока»: одна сессия-владелец, у каждого игрока свой генератор.
|
||
|
||
- **Пакеты:** `duels/mode/DuelsMode` (`implements MultiMode`), `duels/generator/DuelsGenerator`
|
||
(owner, `Map<ParkourPlayer, SingleDuelsGenerator>`, allowJoining, goal, spawnData),
|
||
`duels/generator/SingleDuelsGenerator` (`extends ParkourGenerator` со своим `state`/`history`/цветом/островом),
|
||
`duels/match/` (goal, счёт, initCountdown, win с guard `stopped`, идемпотентный синхронный `reset(false)`),
|
||
`duels/menu/MultiplayerMenu` + кнопка в `PlayMenu`.
|
||
- **Переиспользуют ядро:** `ParkourGenerator` (база), `BlockPlacer` + `block/*`, `PlayerInteractionHandler`
|
||
(скоринг по своей трассе), `ParkourHotbar` (баннер-старт), `PreviousData` (сейв/восстановление),
|
||
`Session`, `UserRegistry`. Результаты НЕ пишутся в лидерборд (`getLeaderboard() = null`).
|
||
- **Требования к ядру:** хук `selectBlockData()` (фиксированный цвет на игрока), пер-игроковая спавн-площадка
|
||
со сдвигом, ослабить `final class Island`, leave() без `ParkourUser.leave`-рекурсии (guard-флаг, п.24).
|
||
- **Не повторять баги Race/Coop:** без TIME-лидерборда (время брать своим форматтером из `state.start`),
|
||
ресет только синхронно с null-guard, сессия создаётся в `create()`, `join()` через `isAcceptingPlayers`.
|
||
|
||
### Elytra — `elytra/*` (в терминах задачи `mode/elytra/*`)
|
||
|
||
Один генератор на игрока, трасса = секции узлов → сплайн → «кольца»; **клиент-сайд рендер** без записи в мир.
|
||
|
||
- **Пакеты:** `elytra/section/ElytraSection`, `elytra/section/ElytraKnotDirector` (dx~N(75,15), dy-bias, dz~N(0,35)),
|
||
`elytra/section/ElytraPointType` (CIRCLE/FLAT), `elytra/generator/ElytraGenerator` (`extends ParkourGenerator`,
|
||
свой `tick()`, переопределённый `getMode()`, свой fall-детект — не полагаться на `LifecycleTickManager.checkPlayerFall`),
|
||
`elytra/mode/` (ElytraMode + Default/SpeedDemon/MinSpeed/TimeTrial/Close/Obstacle — по подклассу на режим),
|
||
`elytra/render/` (очередь `Map<chunkX, Set<Vector>>` → `player.sendBlockChanges(states)`, лимит min(viewDistance, 8)).
|
||
- **Переиспользуют ядро:** `ParkourGenerator` (база + профили сложности), `ParkourHotbar`, `PreviousData`,
|
||
`block/client/` (виртуальные блоки), зону `Divider`; скор — double (добавить float/double-вариант `Score` или
|
||
отдельный счётчик).
|
||
- **Требования к ядру:** пер-игроковый `state` (не разделяемый на сессию), высота/порог ASCEND под наши
|
||
zone-границы, раздача элитр в слот нагрудника через `ParkourHotbar`.
|
||
- **Зависимости:** Apache Commons Math (`SplineInterpolator`) или Catmull-Rom — без стороннего мира.
|
||
|
||
Тесты новых режимов (п.131): сплайн/секции, `ElytraGenerator.tick` (score по velocity.x, причины reset),
|
||
`DuelsGenerator.win` (guard/идемпотентность), `SingleDuelsGenerator.score→win` при goal, регрессия рекурсии unregister.
|
||
|
||
## Политика линтеров
|
||
|
||
- **Checkstyle — жёсткий гейт** (`isIgnoreFailures=false`, `maxErrors=0` в `build.gradle.kts`): правило `FileLength`
|
||
(200) и прочие жёсткие проверки падают сборку. Сейчас сборка проходит `0 errors`, но копит **~1429 warning**.
|
||
- **Шумные правила — severity=`warning`, не блокируют** (задано в `checkstyle.xml`): `MagicNumber`,
|
||
`FinalLocalVariable`, `EmptyLineSeparator`, `ClassDataAbstractionCoupling`, `ClassFanOutComplexity`,
|
||
`MissingDeprecated`. Они не требуют супрессий и чистятся отдельным проходом.
|
||
- **Legacy-нарушения — точечные супрессии** в `config/checkstyle/suppressions.xml` (`FileLength`,
|
||
`IllegalCatch`, `NPathComplexity`, `WhitespaceAround`). Каждая запись привязана к конкретному файлу,
|
||
удаляется сразу после декомпозиции/зачистки этого файла. **Новые файлы в супрессии не добавляются.**
|
||
- **PMD — report-only** (`isIgnoreFailures=true`), сейчас **45 нарушений**:
|
||
`CognitiveComplexity(17)` + `CyclomaticComplexity(7)` + `GodClass(4)` + `TooManyMethods(8)` — декомпозиция;
|
||
`AvoidReassigningParameters(7)` — переименование параметров; `EmptyCatchBlock(2)` — комментарий/тип.
|
||
После зачистки (п.137) `isIgnoreFailures` убирается, PMD становится гейтом (п.138).
|
||
- В `TODO.md` «Зачистка варнингов линтеров»: `FinalLocalVariable` ~1034 (чистить вместе с декомпозицией),
|
||
`MagicNumber` ~346, `WhitespaceAround` 30, `NPathComplexity` 29, `IllegalCatch` 22.
|
||
|
||
## Порядок работ
|
||
|
||
1. **Архитектура** — утвердить этот документ, зафиксировать целевое дерево пакетов и карту миграции.
|
||
2. **Декомпозиция god-классов** — по карте миграции (BlockPlacer → `block/*`, LoParkourCommand → роутеры,
|
||
PAPIHook → реестры плейсхолдеров, StorageSQL → репозитории + один пул, ParkourPlayer → сервисы,
|
||
InvisibleBarrierMode → `block/render`, ConfigUpdater → мержер и т.д.). После каждого файла — удаление его
|
||
супрессии из `suppressions.xml` (сборка обязана оставаться зелёной).
|
||
3. **Зачистка варнингов** — `FinalLocalVariable`/`MagicNumber`/`WhitespaceAround`/`NPath`/`IllegalCatch`
|
||
по отчётам `build/reports/checkstyle/`; закрыть оставшиеся PMD-нарушения.
|
||
4. **Elytra** — база + режимы + клиент-сайд рендер (меньше зависимостей, чем Duels; S/M-сложность).
|
||
5. **Duels** — `duels/*` с предварительными хуками ядра (`selectBlockData`, пер-игроковый state,
|
||
`final Island`); требует предварительно закрытого Coop-рекурсивного цикла (п.24).
|
||
6. **PMD-гейт** — убрать `isIgnoreFailures=true`, PMD в CI как жёсткий гейт; удалить пустые записи из
|
||
`suppressions.xml`.
|
||
|