- Split by sub-purpose: storage/sql -> schema/+query/+repo/, adaptive/core -> calc/+metrics/+model/, command/admin -> ops/+support/, api/event -> session/+score/+generation/, menu/core -> screen/, config/core -> merge/, config/options -> section/, player/service -> ui/, hook/papi -> resolver/ (done), mode/impl -> solo/+multi/+barrier/ (done) - Move util/gui migration stubs -> menu/gui; lpschem POJOs -> pojo/ - Cross-package classes made public (signatures unchanged); fixed imports - docs/ARCHITECTURE.md + TODO.md updated - Build green: checkstyle 0 errors, PMD 34
44 KiB
LoParkour Bug Tracker & Task List
✅ Исправлено (история, 19/20)
CRITICAL (7/7 fixed)
1. ProfileValue.asInt/asDouble — NumberFormatException при пустой строке✅2. Bukkit.createBlockData — IllegalArgumentException при невалидном materialName✅3. JumpDirector диагональный дрейф — heading никогда не выпрямляется✅4. subList().clear() на synchronizedList(LinkedList) — повреждение node связей✅5. Мультиплеер — runaway генерация блоков✅6. Система схематик полностью сломана✅7. Прыжки за границами валидации✅
HIGH (4/4 fixed)
8. angleInY вращает offset относительно глобального Option.HEADING✅9. Дублирование canJump(Location) и canJump(Vector) — рассинхронизация✅10. synchronizedList — итерации без синхронизации✅11. heading.clone() без null-проверки✅
MEDIUM (5/6 fixed)
12. BlockSelector.selectBlockData рекурсия при пустом списке стилей✅13. checkPlayerFall — break вместо continue✅14. removeTrailBlocks сдвигает индексы истории✅15. Дублирование isSlabMaterial✅16. DifficultyScore thresholds для льда (1.0/2.0)✅- 17. schematicBlocks — ArrayList без thread safety — main-thread-only, неактуально
LOW (3/3 fixed)
18. scoredBlocks HashSet не очищается при fall✅19. getFormattedTime() — Date(elapsed) хак✅20. isUnsafeParkourMaterial блокирует PUMPKIN и _BED✅
🆕 Открыто после полного аудита (2026-08-05)
Пометки: [P1] критично, [P2] высоко, [P3] средне, [P4] низко.
🔴 [P1] Критическое — чинить в первую очередь
Функциональность, которая не работает
- 21. Folia-поддержка — фикция. Нет
folia-plugin.yml(Folia не загрузит плагин).MetricsCollectorиспользует удалённое APIBukkitRunnable.runTaskTimerAsynchronously(MetricsCollector.java:73).SessionStateManager.runTimer→ GlobalRegionScheduler, но тик мутирует блоки/entities (SessionStateManager.java:76, LifecycleTickManager.java:75, BlockPlacer.java:158, teleport в ParkourUser.java:93-95). → Либо выпилить бейдж Folia из README, либо: добавить folia-plugin.yml, перевести тик на runTimerAtLocation/runAtEntity, заменить BukkitRunnable. - 22. Ghost-система — 100% мёртвый код.
GhostManager/GhostRecorder/GhostPlayerнигде не инстанцируются (grep: только само-ссылки). Конфигghost-mode.*не потребляется. → Подключить к lifecycle (запись в saveGhost/спавн в spawnGhosts) ИЛИ удалить подсистему и конфиг. - 23. Coop-режим нерабочий. Ни одно
Session.createне строитCoopGenerator(CoopMode.java:134)./parkour join <игрок>ведёт в спектаторы. → Реализовать создание coop-сессии (лобби/инвайты) или выпилить режим из меню и регистрации. - 24. Coop: латентный StackOverflow.
ParkourPlayer.unregister()→CoopMode.leave()→ParkourUser.leave()→UserRegistry.unregister()→user.unregister()— бесконечная рекурсия (ParkourPlayer.java:102-107, CoopMode.java:115-121, UserRegistry.java:58-67). Нет guard-флага. → Добавить in-flight guard в unregister (например, reentrancy flag в ParkourUser). - 25. Лидерборд: тайминги сломаны.
Score.getTimeMillis()ждёт ровно 3 части через:(Score.java:36-55), но генератор пишетmm:ss.SSS(2 части) иHH:mm:ss.SSS(третья02.123→ NFE). Итог: TIME-сортировка и тай-брейк мёртвые, Race-лидерборд не обновляется. Плюс переполнениеintв m601000+... (Score.java:51). → Унифицировать формат времени; парситьmm:ss.SSSиHH:mm:ss.SSS; перейти наlong. - 26. ConfigUpdater уничтожает пользовательские данные.
merge()перегенерирует весь файл из шаблона при каждом старте (ConfigUpdater.java:85-127): теряются листы (options.leads.amount, стили), кастомные ключи, комментарии; алгоритм отступов (prevIndent -= 2) ошибочен;ignoredSectionsне защищают. → Точечный merge только отсутствующих ключей, сохранять листы/комментарии, либо заменить на библиотеку (e.g.me.mattstudios/ SimpleConfig) илиBukkit's config addDefaults. - 27. SQL: утечка Hikari-пулов.
StorageSQL.init(mode)вызывается на каждый режим (6 лидербордов), каждый раз пересоздаётconnectionManager(StorageSQL.java:66-77, LeaderboardStorage.java:35) → до ~10 соединений висят навсегда. → Один пул на плагин, инициализация один раз. - 28. SQL: блокирующий connect на main thread (SQLConnectionManager.java:34-56) — при лежащей БД включение висит до 5с×режимы. →
setInitializationFailTimeout(-1)+ асинхронный connect. - 29. SQL: адаптив не стартует.
AdaptiveServices.initвызывается до создания лидербордов, аStorageSQL.runWhenConnectedвыбрасывает колбэк при не-подключении (StorageSQL.java:45-51). → Очередь колбэков в SQLConnectionManager.onConnectCallbacks; порядок init: лидерборды → storage → adaptive. - 30. SQL: настройки игрока не сохраняются.
SQLDataMapper.serializeSettingsвсегда"{}", десериализация no-op (SQLDataMapper.java:105-115) → при входеschematicDifficulty/blockLeadи др. = null → NPE вParkourPlayer.updateGeneratorSettings:157-164иPAPIHook.java:140. → Реализовать сериализацию/десериализацию всех настроек (JSON-колонка) + fallback на defaults. - 31. Схематики: креш на main thread от битых файлов.
LPSchematicLegacy/SchemLoaderпарсят без валидации; NPE/AIOOBE мимоcatch(IOException)(SchematicManager.java:109-133, LpschemConverter.java:44-54). Битый файл = отказ включения плагина. → Валидация dimensions/palette/frameCount +catch(RuntimeException)+ лимиты размера файла/блоков. - 32. JumpCalculator: возврат невалидного прыжка. После 10 неудач валидация сдаётся и возвращает заведомо невалидный блок (JumpCalculator.java:125-136). → Пере-роллить offset/направление, а не только уменьшать distance/height; при исчерпании — safety-fallback (простой допустимый шаг).
- 33. JumpCalculator: диагональный heading залипает.
JumpDirector.getRecommendedHeadingв безопасной зоне возвращает current без изменений (JumpDirector.java:105-107), аcalculateJumpOffsetприoffset.getX()!=0перезаписывает Z (JumpCalculator.java:110-115) → курс зигзагом и не выпрямляется. → При диагонали аппендикс не перезаписывать случайным; корректный поворот-домножитель.
Генерация / lifecycle
- 34. Удаление неверных блоков схем.
handleSchematicEndBlock→removeTrailBlocks(0)удаляет старейшие 20 блоков курса, а не схематику (PlayerInteractionHandler.java:133-142); блоки схематики остаются в мире как скоробл. → Хранить координаты схематики отдельно и чистить их; не смешивать с history. - 35. Ложные падения.
fallThreshold = spawnY - 30при спуске ~2 блока/прыжок → длинный спуск (>15 прыжков) ресетит живого игрока (LifecycleTickManager.java:72). → Считать порог относительно последнего блока / прогресса, а не спавна. - 36. Генерация вхолостую.
maintainBlockLeadсравниваетscore+2сhistory.size(), ноcleanupDistantBlocksсрезает голову истории каждые 100 тиков → 5 блоков/тик генерится даже когда игрок стоит (LifecycleTickManager.java:89-98). → Считать lead по координате последнего сгенерированного блока, а не size истории. - 37. One-time награды повторяются.
collectedRewardsнигде не читается (LifecycleEventManager.java:82-86) → награда за N очков выдаётся в каждом забеге. → Проверять и помечать collectedRewards при выдаче. - 38. Мёртвый конфиг генератора.
jump-validation.*(JumpValidator жёстко зашит: 4.5/4.1/1.25/3.0),jump-types.*,ghost-mode.*,useSpecialBlocks,blockLead,EffectManager— не читаются нигде. → Подключить или удалить из config.yml/generation.yml.
🟠 [P2] Высокое
Потокобезопасность
- 39.
Divider.SECTIONS— обычный HashMap, синхронизирован только add (Divider.java:26,33-54); итерации на quit-патче и в листенерах без блокировки → CME. → ConcurrentHashMap или полная синхронизация read-путей. - 40.
PlayerMetricsне потокобезопасен при том, что тик-поток пишет, а async-автосейв (5 мин) читает (MetricsCollector.java:148-190; jumpTypeStats — HashMap). → copy-on-read или атомарные поля/ConcurrentHashMap. - 41.
FileStatsStorage— гонка read-modify-write между тиком (fall) и async-сейвом → потерянные апдейты/битые JSON (FileStatsStorage.java:53-67,94-114). → Сериализация записи (ReentrantLock / очередь сейвов). - 42.
Leaderboard.putсортирует вне лока, а async-таймер мутирует карту (Leaderboard.java:83-96, LeaderboardStorage.java:63-73) → CME и окно пустого/неверного лидерборда. → Сортировка подsynchronized(scores); не хранить пре-сортированную карту. - 43.
StorageDisk— неатомарная запись файла (таймер + синхронный shutdown пишут одновременно). → tmp-файл + rename, файловый лок. - 44.
onChat(async) итерирует сессионные ArrayList/Divider.SECTIONS(ParkourRestrictionListener.java:36-60) → CME. → Снимок списков или синхронизация.
Player
- 45.
PreviousDataне восстанавливает: скорость ходьбы/полёта, атрибуты, опыт, невидимость, питомцев, коллайдер, чужой скорборд (PreviousData.java:26-95; ParkourPlayer.java:62-64 принудительно снимает flight/invisible). → Расширить capture/restore; восстановление исходного scoreboard. - 46.
Storage.readPlayerсинхронно на main thread при join (UserRegistry.java:46) — файл/DB блокируют тик. → Async-чтение с кэшем или схемой «join сразу, подгрузка настроек позже». - 47.
UserRegistry.getUsers()строит полный Set на каждый BlockBreak/Place/Drop/Damage (UserRegistry.java:94-98) → O(n) аллокаций на горячем пути. → КэшMap<UUID, ParkourUser>+ привязка через PersistentDataContainer или entity. - 48.
sendTranslated(...).formatted()бросает IllegalFormatException при%в нике/сообщении (ParkourUser.java:102, Locales.java:76-82). → Обернуть в try или использовать replace-based формат. - 49.
getJoinCount— int между main и async (bStats) (UserRegistry.java:100-104). → AtomicInteger.
Modes
- 50.
GravityShiftModeне снимает эффекты при выходе игрока (только в reset для присутствующих, GravityShiftMode.java:149-163). → Снимать в unregister. - 51.
RaceMode.finishRace—reset(false)оставляет игрока в воздухе (RaceMode.java:142-147; GeneratorCleanup.reset чистит курс без регенерации/телепорта). → Телепорт в лобби + регенерация старта. - 52.
SpeedrunModeоставляет удалённые блоки вhistory→ ломает координатные поиски. → Удалять координату из history при снятии блока.
Схематики (жизнь в игре)
- 53. Паста схематики синхронно на main thread (BlockPlacer.java:192-226) — большая схема = фриз. → Async-паста с FastAsyncWorldEdit или chunk-снапшоты.
- 54.
StructurePasterзаписывает в «схему» все не-air блоки бокса, включая чужие (платформа, курс) (StructurePaster.java:44-54) → потом удаляются. → Записывать только блоки, поставленные самой пастой (сравнивать с snapshot до). - 55.
SchemPasterиспользуетgetMinimumPoint()вместоgetOrigin()(SchemPaster.java:27,36) → смещённая вставка. →clipboard.getOrigin(). - 56. Загрузка всех схем синхронно на enable/reload (SchematicManager.java:87-133). → Async-загрузка с прогрессом.
Призы / награды
- 57.
%player%черезString.replaceAll—$/\в нике кидают IllegalArgumentException (Reward.java:43-45). →replace(..., literal). - 58.
contains("vault:")case-insensitive, ноreplaceFirst("vault:")— нет →VAULT:100падает с NFE (Reward.java:55-61). → Единый lowercase-обработчик. - 59. Нет валидации сумм Vault —
vault:-100уводит баланс в минус (Rewards.parseScores). → Клэмпить >=0 или блокировать отрицательные.
🟡 [P3] Среднее
SQL / storage
- 60.
SQLDataMapperUUID.fromString кидает unchecked IllegalArgumentException (SQLDataMapper.java:65,31) мимо catch(SQLException) → битые строки роняют чтение. → Ловить и логировать/скипать строку. - 61.
SQLQueryExecutor.prepareStatement(без callers) никогда не закрывает connection → утечка пула при будущем использовании. → try-with-resources / borrow-tracking. - 62.
SQLMigrationManagerмёртв;migration_adaptive.sqlссылается на несуществующуюloparkour_scoresи нигде не исполняется; та же DDL в 3 копиях; префиксSQL_PREFIXприменён непоследовательно. → Единый мигратор с версионированием; починить/удалить SQL-файл. - 63.
writeScoresгоняет всю таблицу туда-сюда без дельт (StorageSQL.java:105-128). → Инкрементальные upsert'ы только изменённых записей. - 64.
Score.fromString— CSV по запятой: ник с,ломает все поля (Score.java:19-31). → Экранирование или JSON. - 65.
validateConnectionмутируетOption.SQL=falseпри транзиентном сбое (SQLConnectionManager.java:81-86) — SQL отключается навсегда. → Отдельный флаг/ретрай.
Adaptive
- 66.
NEAR_MISS_THRESHOLD=3.5хардкод при живом конфигеadaptive.near-miss-distance(0.3) (MetricsCollector.java:32-33);getNearMissDistance/getUpdateInterval/getConfidenceThresholdбез callers. → Подключить конфиг. - 67. Разные формулы confidence при записи и чтении (SkillRating.java:54-62 vs SQLStatsStorage.java:48). → Единый метод.
- 68. Мёртвый API адаптива:
estimateMinRating/estimateMaxRating/calculateRecommendedRating/isReadyForIncrease/shouldDecrease/smoothTransition/estimateDifficulty/getEffectiveRating,incrementJumps/incrementFalls/updateLongestStreak,PendingUpdate.flushBatch,NoOpStatsRepository. → Удалить или до-реализовать. - 69.
reload()гоняетsaveAllMetrics()на command thread (AdaptiveServices.java:52-63). → Async.
Config / reload
- 70.
/lp reloadне атомарен: дублируетRegistry.STYLES(никогда не чистится), оставляет старыеRewardsприenabled:false,BORDER_SIZEустаревший, миры/генераторы не обновляются (ConfigUpdater, OptionStyles.java:23-43, Rewards.java:41-52, OptionGeneral.java:38-40). → Полный reset registry + пересоздание зависимых подсистем. - 71.
LocaleLoaderпри ошибке подменяет кэш на emptyMap → все сообщения пустые (LocaleCache.java:22-25). → Сохранять старый кэш при сбое. - 72. Нет fallback на en для пропущенных ключей/неизвестных локалей (Locales.java:69-71). → Fallback-цепочка locale → en.
- 73.
Rewards.init()приenabled:falseне чистит статические мапы → старые награды продолжают выдаваться после reload. → Очистка. - 74.
ConfigAccessorтипо-опечатка тихо даёт false/0 (ConfigAccessor.java:45-48). → Строгая валидация ключей при старте.
Session
- 75.
catch(Throwable)вокругgenerator.tick()без стека + сообщение игрокам (SessionStateManager.java:80-83). → Логировать стек, не спамить чат. - 76.
SessionUserManager— целиком мёртвый дубликат SessionPlayerManager;updateVisibilityO(n²) showPlayer. → Удалить/объединить, кэшировать видимость. - 77.
Session.generator— публичное mutable поле (Session.java:23). → private + getter.
Menu / GUI
- 78.
currentPage— общий поле-синглтон на всех игроков (SingleMenu.java:20, SpectatorMenu, LeaderboardsMenu, SingleLeaderboardMenu) — двое листают → перезаписывают друг друга. → Per-player состояние (Map<UUID, Integer> или GUI-инстанс на игрока). - 79. Скилл-головы на лидерборде не рендерятся:
setOwningPlayer, затемskull.setItemMeta(built)метой STONE (SingleLeaderboardMenu.java:53-80;head.materialпуст). → Применять name/lore к той же SkullMeta, не перезаписывать. - 80.
LangMenuпоказывает первые 7 неупорядоченных ключей без пагинации (LangMenu.java:37-54);ParkourSettingsMenuобрезает стили на 7. → Сортировка + пагинация. - 81. Клик-обработчики не проверяют игрока — общий стейт меню. → Привязка к игроку.
Command / security
- 82.
getCommand("LoParkour")без null-guard (PluginBootstrap.java:118-119) — работает только из-за lowercase-fallback. → Null-проверка. - 83. Схематик-хендлеры без собственных permission-проверок (SchematicCommandHandler) — всё держится на вызывающем. → Проверка в каждом handler.
- 84.
SchematicCommandHandler.SELECTIONSкэш ключён по Player и не чистится на выход → утечка памяти (SchematicCommandHandler.java:38,58-71). → Удалять при quit/инвалидации. - 85. Tab-completion предлагает отключённые режимы (LoParkourCommand.java:232-236);
Registry.getModecase-sensitive, лидерборд-аргумент — нет. → Унифицировать case/фильтр по enabled. - 86.
admin-нода даёт доступ кrecoverinventoryдругого игрока и глобальному reset (ParkourOption.java:49). → Разделить permission-ноды.
Hooks / util
- 87.
HoloHookчитаетsplit[1],split[2]без проверки длины → AIOOBE (HoloHook.java:35-50). → Валидация аргументов. - 88. PAPI expansion id =
"witp"(PAPIHook.java:27) — наследие форка «Walk in the Park» (оригинального Infinite Parkour), не совпадает с документацией/скрорбордом. → Переименовать вloparkour(см. п.118). - 89.
VaultHookкэширует Economy навсегда (VaultHook.java:26-35) — после reload экономики ссылка битая. → Re-resolve при каждом использовании/слушать ServiceEvent. - 90.
Probs.random()на пустой карте — NoSuchElementException (Probs.java:34-48; достижимо при инвертированных min>max или невалидных special-материалах); все нулевые веса → всегда первый элемент. → Empty-check + отсев нулевых весов. - 91.
ParticleUtil.box— бесконечный цикл приspacing<=0(ParticleUtil.java:36-55). → Guard. - 92.
Locations.max/minиSkullSetter— мёртвый код;Item.clone()теряет meta. → Удалить/починить. - 93.
en.yml:258— висячий плейсхолдерserver.ipв скорборде. → Убрать или подставить.
⚪ [P4] Низкое / мелочи
- 94. Миграция схем при загрузке нерекурсивна и пишет
schematics.ymlпри каждом конверте (LpschemConverter.java:63-84). - 95.
SchematicIdGeneratorхеширует только byte-координаты → коллизии id (SchematicIdGenerator.java:23-28). - 96.
parseDifficultyпринимает NaN/Infinity (SchematicCreator.java:64-70) → схема навсегда невыбираема. - 97.
stem()регистрирует файл.nbtпод пустым id;.NBTигнорируется,.schematic— case-insensitive (SchematicManager.java:147-150,88). - 98.
Locales.getPlayerLocale/ParkourUser.localederefOPTIONS_DEFAULTS.get(LANG)без null-check (Locales.java:170-184, ParkourUser.java:64). - 99. Магические числа: fall-threshold -30 (LifecycleTickManager.java:72), render 24.0/0.1/%4 (InvisibleBarrierMode.java:67-68,98), 10/30 (GravityShiftMode.java:82,108), 100 тиков (RaceMode.java:147), hotbar delay 5 (ParkourPlayer.java:192),
TICKS_PER_SECOND=20переопределён (SpeedrunMode.java:82). → В конфиг/константы. - 100. Deprecated API:
Material.STONE_SLAB(MaterialUtil.java:20),registerNewObjective(String,String,String)(ScoreboardManager.java:75),getOfflinePlayer(name)(AdminCommandHandler.java:186-199), legacy ACTION_BAR (ParkourSpectator.java:73, RaceMode.java:103). - 101.
ParkourSpectator.java:96— бессмысленный double-set spectator target (no-op). - 102.
GeneratorStatistics.registerScoreпишет в лидерборд на каждого игрока при каждом падении в мультиплеере (GeneratorStatistics.java:46-55). - 103.
GeneratorProfileManager.calculateSpecialChances— пустойcatch(Exception ignored)(GeneratorProfileManager.java:154) → бесшумно отключает special-блоки; magic-веса 0.3/0.2/0.5 (57-67). - 104.
ProfileValue.asDouble/asIntбросают NFE на нечисловых строках (Profile.java:54,64) — только empty-guard. - 105. Дубликат логики
create()во всех режимах с несогласованными проверками дублей (instanceof DefaultMode vs instanceof SpeedrunGenerator). - 106. Мёртвый код:
EffectManagerцеликом,SessionUserManager,SQLMigrationManager,SQLQueryExecutor,SkullSetter,Locations.max/min,ParkourBlockGenerateEvent,onPlayerJoin(CoopMode.java:169),PLAYER_COLUMNS/OptionContainer(ParkourPlayer.java:38,205),acceptingPlayers-механизм,Mutedв двух местах. - 107. Публичные mutable поля-государство:
GeneratorState— 15 публичных полей,ParkourGenerator.session/island/profile/state/generatorOptionsпубличные final,Scoreвремени в строке. → Инкапсуляция.
🧪 Тесты
- 108. Расширить покрытие (сейчас 4 класса, 398 строк): минимум —
Score.getTimeMillis,JumpCalculator/JumpValidator(включая retry-exhaustion и диагональ),ConfigUpdater.merge(листы/комментарии/пропуски),Probs.random(пустые/нулевые веса),Reward(vault-кейс,%player%с$),SQLDataMapper(UUID/настройки),PreviousDatarestore. - 109. Проверять Folia-инварианты в CI (запрет BukkitRunnable/global-мутаций) — статический анализ или правило checkstyle.
📊 Конкуренты — куда догонять
- 110. Режимы: IP-Reborn имеет 8 рабочих режимов (Practice/Speed/SuperJump/Hourglass/TimeTrial/Wave/TeamSurvival/Duels) + elytra. У нас 5 работают, coop мёртв, elytra удалён. → Довести coop до рабочего состояния или убрать из рекламы; рассмотреть TimeTrial/Hourglass.
- 111. Мультиплеер-лобби с инвайтами — у IP-Reborn есть, у нас нет (только dead coop). → Реализовать invite-флоу.
- 112. Void-генератор: IP-Reborn встроил native void-gen (не нужен VoidGen). У нас зависимость от VoidGen в softdepend и внешнего мира. → Встроить void-gen.
- 113. 6 типов прыжков (ice/slab/fence/glass/schematic) — у нас slab/ice/schematic есть, fence/glass-pane через jump-types (мёртвый конфиг). → Догнать типологию прыжков.
- 114. Документация/вики + Discord — у конкурентов полноценная вики. → Сделать документацию по config.yml, generation.yml, rewards-v2.yml, локалям, API.
- 115. Версионность: конкурент держит 1.7–26.x. Наш api-version 1.20, Java 21+. → Определить целевые версии и CI-тесты на них.
- 116. Инвентарь-recovery как фича поддержки — вынести в команду и проверять (у нас есть, но с гонками потоков, см. InventoryData.java:50-57).
Наследие форка (LoParkour = форк «Walk in the Park» / Infinite Parkour Efnilite)
- 117. Удалить формат
.lpschemполностью, оставить только.schem(WorldEdit) +.nbt. Выпилить: пакетschematic/legacy/lpschem/,LpschemConverter, конвертер-команду,LPSchematicLegacy, примеры*.lpschemизresources/schematics/, все ветки.lpschemвSchematicManager/SchematicCreator. Формат устарел, это двойная поддержка (аудита: п.31, 53-56, 94-97) — один формат схем, один путь пасты/валидации. - 118. Переименовать PAPI-id
witp→loparkour(наследие оригинала; также поменять примеры в scoreboard/docs). - 119. Command block support — у IP и IP-Reborn есть (запуск команд
/parkourиз командного блока), у нас нет. → ПоддержатьCommandBlocksender вLoParkourCommand. - 120. Расширить PAPI-плейсхолдеры до 15+ (у IP 15+): score, record, время, режим, ранк, стиль, скорость, статистика адаптива и т.д.
- 121. «No-lag scoreboard» — у IP заявлен, у нас скорборд пересобирается каждый тик (LifecycleTickManager.java:61, ScoreboardManager.update). → Обновлять title/lines только при изменении значений (debounce/версия).
Таблица сравнения (полная)
| Возможность | LoParkour 1.3.3 | Ориг. Infinite Parkour 5.3.1 (заброшен, 10.2024) | IP-Reborn 6.0 | ajParkour 2.12 (free) | Parkour (A5H73Y) 7.2.8 |
|---|---|---|---|---|---|
| Бесконечная генерация | ✅ | ✅ (6 типов) | ✅ (6 типов) | ✅ в зоне (WorldEdit) | ❌ (карты/чекпоинты) |
| Режимы | 7 заявлено, coop мёртв | ✅ | 8 + elytra | 5 уровней difficulty | ❌ |
| Ghost-реплей | ❌ мёртвый код | ❌ | ❌ | ❌ | ❌ |
| Схематики | ✅ (.lpschem/.schem, крешит) | ✅ | ✅ | ❌ | ✅ паркур-блоки |
| Лидерборды | ✅ (TIME сломан) | ✅ | ✅ | ✅ top-10 | ✅ время/смерти/уровень |
| Storage | YAML+MySQL (утечки) | Local+MySQL | Local+MySQL | YAML+MySQL | YAML+MySQL |
| Inventory recovery | ✅ частично | ✅ | ✅ | ✅ | ❌ |
| Мультиплеер-лобби | ❌ coop мёртв | ❌ (IPPlus отдельно) | ✅ сессии/инвайты | ❌ | ❌ |
| PAPI / Vault / HD / Floodgate | ✅ (id witp) | ✅ 15+ PAPI | ✅ | ✅ | ✅ широчайшие |
| Command block | ❌ | ✅ | ✅ | ❌ | ❌ |
| Переводы | en/ru, без fallback | en/nl + custom | en/zh + custom | messages.yml | strings.yml |
| Dev API | ✅ события + Registry | ✅ | ✅ | ❌ | ✅ лучшая документированность |
| Folia | ❌ заявлено, не работает | ❌ | ❌ честно отключено | ❌ | ❌ |
| Версии | 1.19.2+ / Java 21 | 1.16–1.21 | 1.21.11–26.x / Java 21-25 | 1.7.10–26.x | 1.7–26.x |
| Актуальность | 2026, 1.3.3 | заброшен | v6, активный | 178 обновлений | с 2012 |
| Документация | минимальная | вики (осталась) | вики + дискорд | вики | полная вики |
Ссылки: IP-Reborn — https://www.spigotmc.org/resources/infinite-parkour-reborn-3-in-1.136046/ + https://github.com/LostUmbrella58/IP-Reborn · Ориг. IP — https://www.spigotmc.org/resources/87226/ + https://github.com/Efnilite/Walk-in-the-Park
🆕 Новые режимы (анализ исходников IP-Reborn в ref/IP-Reborn-main/)
Duels (перенос из dev.efnilite.ipp)
- 122. Duels — модель «генератор на игрока». Сейчас наш
ParkourGenerator= один скорер на сессию (общийGeneratorState/history/score,PlayerInteractionHandler.checkAllPlayersскорит всех по одной трассе) — это несовместимо с дуэлью. Схема IP-Reborn:DuelsGenerator(владелец сессии) держитMap<ParkourPlayer, SingleDuelsGenerator>, каждыйSingleDuelsGenerator extends ParkourGeneratorсо своим state/history/time/цветом/спавн-островом, разнесены по Z. Победа = первый набравшийgoal. Результаты НЕ пишутся в лидерборд (getLeaderboard()=null). Тик идёт только через владельца. - 123. Duels — изменения ядра: ослабить
final class Island(или строить арены вручную), дать хукselectBlockData()(фиксированный цвет на игрока), пер-игроковая спавн-площадка со сдвигом. - 124. Duels — классы:
DuelsMode implements MultiMode(create() реально создаёт сессию, join() черезisAcceptingPlayers, leave() толькоremovePlayerбезParkourUser.leave— фикс рекурсии Coop),DuelsGenerator(owner/allowJoining/playerGenerators/spawnData/goal;initCountdown()по клику LIME_BANNER 10с;win()с guardstopped;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. - 126. Duels — не повторить баги: Race (TIME-лидерборд, ресет в воздухе через
runLater) — у Duels лидерборда нет, время брать изstate.startсвоим форматтером, ресет только синхронно с null-guard; Coop (рекурсия, невозможность создать) — не вызыватьParkourUser.leaveповерх unregister, сессию создавать вcreate().
Elytra (порт из dev.efnilite.iep, Kotlin → Java)
-
127. Elytra — модель. Один генератор на игрока (не сессия на многих). Трасса = секция из 5 узлов (
KnotDirector: dxN(75,15), dy bias −15/+15 (ASCEND при Y<50), dzN(0,35)), узлы → кубический сплайн → точки → «кольца» (Брезенхэм,PointType.CIRCLE(-1)/FLAT(-5)для CloseMode). Очки = пройденное расстояние: каждый тикmovementScore += velocity.xприisGliding && x > blockSpawn.x. Фолл: Y<спавн−radius | дальше radius от точек | не глидишь. Подъёмная секция = фейерверк-буст (velocity = v*0.7+dir*0.6, насильныйisGliding). -
128. Elytra — клиент-сайд рендер.
ClientBlockChangerНЕ ставит блоки в мир: очередьMap<chunkX, Set<Vector>>, каждый тик шлёт игрокуplayer.sendBlockChanges(states)(MultiBlockChange) только для чанков вперёд по X (limit min(viewDistance,8)). ПакетElytraBlockChangerна Java. -
129. Elytra — классы:
ElytraSection+ElytraKnotDirector+ElytraPointType,ElytraGenerator extends ParkourGenerator(СВОЙtick(), переопределённыйgetMode(), свой фолл-детект — не полагаться наLifecycleTickManager.checkPlayerFall), режимыElytraMode/Default/SpeedDemon/MinSpeed/TimeTrial/Close/Obstacle(по одному подклассу генератора), раздача элитр в слот нагрудника +ParkourHotbar,PreviousDataпереиспользуем. -
130. Elytra — зависимости/мир. Добавить Apache Commons Math (
SplineInterpolator) или заменить на Catmull-Rom. Отдельный мир не нужен: труба клиент-сайд, наш мир void; подобрать высоту/порог ASCEND под наши zone-границы. Скор в double (нашиScore.scoreint — масштабировать или добавить float-вариант). Сложность: S базовый, M режимы+бусты, L Obstacle+настройки+стили+награды. -
131. Тесты под новые режимы: сплайн/секции (изоляция, Y/Z интерполяция),
ElytraGenerator.tick(score по velocity.x, shouldReset-причины),DuelsGenerator.win(guardstopped, идемпотентностьreset(false)),SingleDuelsGenerator.score→win при goal, рекурсия unregister (регрессия Coop).
🧹 Зачистка варнингов линтеров (добавлено после ужесточения конфигов)
Checkstyle отработал 0 errors (error-уровневые legacy-нарушения IllegalCatch / NPathComplexity / WhitespaceAround закрыты супрессиями в config/checkstyle/suppressions.xml), но остаётся 1429 warning. PMD — 45 нарушений (report-only, isIgnoreFailures=true). Статистика по файлам — в build/reports/checkstyle/main.xml и build/reports/pmd/main.xml.
✅ ПРОГРЕСС (этап «декомпозиция god-классов» ЗАВЕРШЁН)
Документ структуры:
docs/ARCHITECTURE.md. Все 11 god-классов >200 строк декомпозированы (супрессии FileLength сняты, каждый файл ≤200 строк): LoParkourCommand→CommandRouter/CommandTabCompleter/ JoinCommandExecutor/LeaderboardCommandExecutor/CommandUtil; SchematicCommandHandler→SchematicDispatcher/SchematicHandler/ SchematicWand/SelectionStore/SchematicMessenger; ConfigUpdater→ConfigMergeParser/ConfigMergeProcessor; BlockPlacer→BlockTargetSelector/BlockWorldPlacer/SchematicBlockPlacer; InvisibleBarrierMode→BarrierGenerator/BarrierRenderer; PAPIHook→PlaceholderDispatcher&Resolvers&Formatters; AdminCommandHandler→ForceJoin/ForceLeave/Reset/RecoverInventory/ AdminTargetResolver/AdminResponses; MetricsCollector→MetricsCacheStore/MetricEventCollector/JumpTimingTracker; StorageSQL→SQLSchemaManager/SQLScoreRepository/SQLPlayerRepository; ParkourPlayer→ScoringTracker/Persistence/SpawnSetup; Leaderboard→LeaderboardRecords. Актуальные счётчики: checkstyle 0 errors / 1413 warnings, PMD 34. Пункты 132–138 — в силе.✅ ПРОГРЕСС-2 (правило «одна папка = одна цель» — применено полностью)
Правило в
docs/ARCHITECTURE.md(п.8). Выполнено разбиение переполненных пакетов:command/→core/+util/,mode/impl/→solo/+multi/+barrier/,hook/papi/→resolver/,storage/sql/→schema/+query/+repo/,adaptive/core/→calc/+metrics/+model/,api/event/→session/+score/+generation/,config/core/→merge/,config/options/→section/,player/service/→ui/,menu/core/→screen/,util/gui/→menu/gui/,schematic/legacy/lpschem/→pojo/. Пересекающие пакеты классы стали public (сигнатуры не менялись). Сборка: checkstyle 0 errors / 1413 warnings, PMD 34, build ✓.
- 132. [C] Почистить
FinalLocalVariable(legacy, ~1034 warning / 120 файлов). Объявить локальные переменныеfinal. Делать вместе с декомпозицией, а не отдельным проходом. - 133. [C] Почистить
MagicNumber(legacy, ~346 warning / 60 файлов). Заменить магические числа на именованные константы (приоритет: конфиг- и стиль-коэффициенты, пороги сложности, тайминги). - 134. [M] Починить
WhitespaceAround(legacy, 30 / 14 файлов). Авто-формат либо ручная правка по отчёту. - 135. [M] Починить
NPathComplexity(legacy, 29 / 25 файлов). Решается декомпозицией методов (файлы и так в списке на рефакторинг). - 136. [M] Починить
IllegalCatch(legacy, 22 / 19 файлов). catch(Exception/Throwable/RuntimeException)→сужение до конкретных исключений либо переименовать вexpected|ignore. - 137. [M] Починить все нарушения PMD (45):
CognitiveComplexity(17)+CyclomaticComplexity(7)— декомпозиция;GodClass(4)+TooManyMethods(8)— декомпозиция;AvoidReassigningParameters(7)— переименование параметров;EmptyCatchBlock(2)— комментарий/конкретный тип. - 138. [S] Перевести PMD из report-only в жёсткий гейт. После зачистки убрать
isIgnoreFailures=trueвbuild.gradle.kts(pmd-блок).
После зачистки п.132–137 — удалить соответствующие записи из config/checkstyle/suppressions.xml (супрессии остаются только для ещё незачищенных legacy-файлов).
Итог по аудиту
- Найдено: ~125 новых пунктов (P1: 18, P2: 21, P3: 28, P4: 14, тесты: 2, конкуренты: 13, наследие форка: 5, новые режимы: 10, зачистка варнингов: 7).
- Мёртвые фичи: ghost (п.22), coop (п.23-24), jump-types/EffectManager (п.38).
- Мёртвый конфиг:
jump-validation,jump-types,ghost-mode,useSpecialBlocks,blockLead. - Главные риски производства: Folia-краш, утечка SQL-пулов, потеря данных в ConfigUpdater, креш от битых схем.