feat: elytra firework boost, obstacle mode, duels menu, tests

- ElytraBoost: one-shot firework boost (v*0.7+dir*0.6, FIREWORKS_SPARK,
  rocket sounds) on entering ascending section + sustained thrust; Particle.
  FIREWORK doesn't exist in spigot-api 1.20.4 -> FIREWORKS_SPARK
- ObstacleGenerator (client-side obstacles: PILLARS/LINES/HOLE_IN_WALL/
  CIRCLE/DIAMOND via ClientBlockChanger.sendNow) + ObstacleMode + locales
- DuelsMenu (lists open duels sessions, click to join) + /parkour duels
  command + tab completion + locales play.multi.duels.{name,menu_empty}
- Tests: ElytraSection/PointType/KnotDirector/SingleDuelsGenerator (15 new)
- checkstyle 0/0 (main+test), PMD 0, 41 tests pass
This commit is contained in:
loki5512344 2026-08-05 21:49:11 +02:00
parent 5707cf2d24
commit 3b5d3d97d5
Signed by: boba
GPG key ID: 253067914055423B
19 changed files with 804 additions and 28 deletions

View file

@ -236,17 +236,17 @@
- **122. Duels — модель «генератор на игрока».** ✅ **СДЕЛАНО**: `duels/` — `DuelsMode implements MultiMode`, `DuelsGenerator extends ParkourGenerator` (владелец: `Map<ParkourPlayer, SingleDuelsGenerator>`, allowJoining, goal, countdown), `SingleDuelsGenerator extends ParkourGenerator` (`getPlayers()=List.of(свой игрок)` → независимый state/history/score; при `state.score >= goal` → `owningGenerator.win`), `DuelsCountdown` (10с отсчёт + старт трасс). Результаты НЕ пишутся в лидерборд (`getLeaderboard()=null`). Тик через владельца (`tick()` тикает каждый single). Зарегистрирован (Modes.DUELS, PluginBootstrap, config.yml `modes.duels.{enabled,max-players,goal,island-distance}`, локали `play.multi.duels.*`).
- **123. Duels — изменения ядра.** ✅ **Добор**: добавлен хук `ParkourGenerator.selectBlockData()` (public, возвращает null по умолчанию) → `BlockSelector.selectBlockData()` первым делом проверяет его; `SingleDuelsGenerator` переопределяет → возвращает цвет игрока (16 concrete-цветов, `setPlayerIndex`) и форсит `defaultChances={DEFAULT:1.0}` + пустые specialChances (курс одноцветный, без спец/схем). Арены — плоские stone-платформы на сдвинутом Z (Island не трогали).
- **124. Duels — классы:** `DuelsMode implements MultiMode` (create() реально создаёт сессию, join() через `isAcceptingPlayers`, **leave() только `removePlayer` без `ParkourUser.leave`** — фикс рекурсии Coop), `DuelsGenerator` (owner/allowJoining/playerGenerators/spawnData/goal; `initCountdown()` по клику LIME_BANNER 10с; `win()` с guard `stopped`; `reset(false)` синхронно и идемпотентно, НЕ `runLater`), `SingleDuelsGenerator` (`getPlayers()=List.of(player)`, `setPlayerIndex`→цвет, `score()`→`win` при goal).
- **125. Duels — интеграция:** `modes.duels{enabled,max:2,target-score,island-distance}` в config.yml, локали `modes.duels.*`/`play.multi.*`, `MultiplayerMenu` (фильтр `instanceof MultiMode`) + кнопка в PlayMenu, баннер-старт в hotbar, `/lp create|join <player>|invite`. Сложность: **L**.
- **125. Duels — интеграция.** Частично: `modes.duels{enabled,max-players,goal,island-distance}` в config.yml, локали `play.multi.duels.*`, баннер-старт владельцу. ✅ **Добор**: `menu/lobby/DuelsMenu` (список открытых дуэльных сессий из Divider.SECTIONS, клик→join) + команда `/parkour duels` (CommandRouter/CommandTabCompleter) + локали `play.multi.duels.{name,menu_empty}`. Осталось: кнопка в PlayMenu.
- **126. Duels — не повторить баги:** Race (TIME-лидерборд, ресет в воздухе через `runLater`) — у Duels лидерборда нет, время брать из `state.start` своим форматтером, ресет только синхронно с null-guard; Coop (рекурсия, невозможность создать) — не вызывать `ParkourUser.leave` поверх unregister, сессию создавать в `create()`.
### Elytra (порт из `dev.efnilite.iep`, Kotlin → Java)
- **127. Elytra — модель.** ~~...~~ ✅ **БАЗОВОЕ СДЕЛАНО**: `elytra/section/` (ElytraSection+KnotDirector+PointType+ClientBlockChanger, сплайн через commons-math3), `elytra/generator/ElytraGenerator extends ParkourGenerator` (свой tick/score/фолл/reset, sustained-буст +0.08 в ascending), `elytra/mode/ElytraMode`. Зарегистрирован (Modes.ELYTRA, PluginBootstrap, локали play.elytra.elytra). Команда commons-math3: `org.apache.commons:commons-math3:3.6.1` + relocate. Осталось по п.127: фейерверк-буст (`v*0.7+dir*0.6`) — сделано упрощённо (только sustained); CloseMode (FLAT-кольца).
- **128. Elytra — клиент-сайд рендер.** ✅ `ClientBlockChanger`: очередь `Map<chunkX, Set<Vector>>`, `player.sendBlockChanges(states)` по чанкам, forward limit min(vd,8), flush/clear. Секции хранят Vector (без пиннинга чанков).
- **129. Elytra — классы.** ✅ Базовый: ElytraSection/KnotDirector/PointType/ClientBlockChanger + ElytraGenerator + ElytraMode. ✅ **M-добор**: подклассы-режимы SpeedDemonGenerator (скор = макс. скорость), MinSpeedGenerator (поддержание мин. скорости, ресет при 6 тиках медленнее), TimeTrialGenerator (скор ограничен SCORE из конфига), CloseGenerator (FLAT-кольца); режимы SpeedDemonMode/MinSpeedMode/TimeTrialMode/CloseMode + регистрация + локали `play.elytra.{speeddemon,minspeed,timetrial,close}` + config `mode-settings.{min-speed,time-trial}`. Добавлен `Config.getDouble(path, default)`. Осталось: фейерверк-буст, раздача элитр через PreviousData, Obstacle-режим.
- **129. Elytra — классы.** ✅ Базовый: ElytraSection/KnotDirector/PointType/ClientBlockChanger + ElytraGenerator + ElytraMode. ✅ **M-добор**: подклассы-режимы SpeedDemonGenerator (скор = макс. скорость), MinSpeedGenerator (поддержание мин. скорости, ресет при 6 тиках медленнее), TimeTrialGenerator (скор ограничен SCORE из конфига), CloseGenerator (FLAT-кольца); режимы SpeedDemonMode/MinSpeedMode/TimeTrialMode/CloseMode + регистрация + локали `play.elytra.{speeddemon,minspeed,timetrial,close}` + config `mode-settings.{min-speed,time-trial}`. Добавлен `Config.getDouble(path, default)`. ✅ **Ещё доборы**: **фейерверк-буст** `elytra/generator/ElytraBoost` (разовый толчок v*0.7+dir*0.6 + частицы FIREWORKS_SPARK + звуки ракеты при входе в ascending, sustained +0.08/тик, трейл каждые 4 тика; Particle.FIREWORK нет в API 1.20.4 → FIREWORKS_SPARK); **Obstacle-режим** (ObstacleGenerator с enum Obstacle PILLARS/LINES/HOLE_IN_WALL/CIRCLE/DIAMOND, клиент-сайд через ClientBlockChanger.sendNow, + ObstacleMode + локали `play.elytra.obstacle`). Осталось: раздача элитр через PreviousData.
- **130. Elytra — зависимости/мир.** ✅ commons-math3 подключён+relocated; отдельный мир не нужен (клиент-сайд); ASCEND_THRESHOLD_Y=50; скор в double (movementScore), в лидерборд пока не пишется (TODO: int-масштаб). Сложность: **S ✅**, M (режимы+бусты) и L (Obstacle+настройки+стили+награды) — далее.
- **131. Тесты под новые режимы:** сплайн/секции (изоляция, Y/Z интерполяция), `ElytraGenerator.tick` (score по velocity.x, shouldReset-причины), `DuelsGenerator.win` (guard `stopped`, идемпотентность `reset(false)`), `SingleDuelsGenerator.score`→win при goal, рекурсия unregister (регрессия Coop).
- **131. Тесты под новые режимы.** ✅ **СДЕЛАНО** (JUnit5+Mockito, 15 новых тестов): `ElytraSectionTest` (сплайн монотонен по X, 5 узлов, isNearPoint, generateBlocks→chunkX, clone), `ElytraPointTypeTest` (CIRCLE 0→1 точка / radius3→24 симметрично, FLAT→3, heightOffset), `ElytraKnotDirectorTest` (bounds dx/dy/dz при seed, разные seed≠), `SingleDuelsGeneratorTest` (16 цветов, fallback, getPlayers без owner). Итог: **41 тест, 0 fail**. Осталось: Coop-регрессия unregister (как п.131 упоминал) — можно добавить позже.
---