refactor: reorganize packages and replace LPSchematic with WorldEdit

Restructure core modules into subpackages (generator, schematic, config, session, etc.); add .schem/.nbt schematic pipeline, commands, adaptive stats skeleton, and bootstrap wiring.
This commit is contained in:
loki5512344 2026-06-01 14:02:48 +02:00
parent 6ba6e5137d
commit fd0ba2cc0e
203 changed files with 5652 additions and 2367 deletions

View file

@ -33,6 +33,15 @@ debug: false
# -= Enable joining =-
joining: true
# -= Adaptive difficulty =-
adaptive:
enabled: true
min-sessions: 3
update-interval: 300
rating-weight: 0.15
confidence-threshold: 0.3
near-miss-distance: 0.3
# -= Proxy options =-
bungeecord:
enabled: false

View file

@ -0,0 +1,79 @@
# Parkour schematics (LoParkour)
Jump structures are loaded from `plugins/LoParkour/schematics/` and matched to a **difficulty** in `plugins/LoParkour/schematics/schematics.yml`.
## Supported file types
| Extension | Source | Notes |
|-----------|--------|--------|
| `.nbt` | Vanilla structure block, `/structure save` | Pasted with Bukkit `StructureManager` |
| `.schem`, `.schematic` | WorldEdit (`//copy`, `//schem save`) | Read via embedded WorldEdit clipboard IO |
| `.lpschem` | **Legacy LoParkour format** | Not used in-game; convert with `/lp schematic convert` |
During parkour generation, when the profile rolls **schematic** type, the plugin picks a random file whose `difficulty` in YAML is **≤** the player's schematic setting (0.0–1.0).
## Difficulty keys (`schematics.yml`)
```yaml
difficulty:
d0e227d2: 0.25 # short id (recommended)
```
For file `parkour-d0e227d2.schem` you can use key `d0e227d2` or the full stem `parkour-d0e227d2`.
Player setting **0.25 (easy)** → only structures with difficulty ≤ 0.25.
**1.0** → any structure.
## Creating schematics in-game (admin)
1. Get the wand: `/lp schematic wand`
2. **Left-click** = position 1, **right-click** = position 2 (or use `/lp schematic pos1` / `pos2`)
3. Save and register:
- `/lp create <difficulty>` — auto name `parkour-<hash>.schem` + YAML entry
- `/lp schematic create <difficulty>` — same
- `/lp schematic create <name> <difficulty>` — custom name (letters, numbers, `-`, `_`)
Difficulty examples: `0.25`, `0.5`, `0.75`, `1.0` (must be between 0 and 1).
4. `/lp schematic reload` — reload files without restart
## Converting old `.lpschem` files
Place `.lpschem` files in `plugins/LoParkour/schematics/` or legacy `schematics-new/`, then:
```
/lp schematic convert
```
Each file becomes `parkour-<id>.schem` in `schematics/` and gets a `difficulty:` entry (from the old metadata, or `0.5` if missing). Original `.lpschem` files are kept; delete them manually after checking.
## Admin commands
| Command | Description |
|---------|-------------|
| `/lp schematic wand` | Selection wand |
| `/lp schematic pos1` / `pos2` | Set corners at your feet |
| `/lp create <difficulty>` | Save selection → `.schem` + YAML |
| `/lp schematic create <name> <difficulty>` | Save with custom name |
| `/lp schematic convert` | Convert all `.lpschem` in schematics folders |
| `/lp schematic list` | List loaded structures |
| `/lp schematic paste <name>` | Test-paste at your location |
| `/lp schematic reload` | Reload disk files |
## Flow (runtime)
```mermaid
flowchart LR
A[BlockPlacer SCHEMATIC roll] --> B[SchematicManager.pick]
B --> C{Format}
C -->|nbt| D[Structure.place]
C -->|schem| E[Clipboard paste]
D --> F[Track blocks / score]
E --> F
```
## Folders
- `plugins/LoParkour/schematics/` — `.nbt`, `.schem`, `.schematic` (active)
- `plugins/LoParkour/schematics/schematics.yml` — difficulty map
- `plugins/LoParkour/schematics-new/` — legacy; scanned only for `.lpschem` convert

View file

@ -2,7 +2,7 @@
# By loki
# Copy this file and rename it to add your own language!
# Materials have to be from: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Material.html
# Learn more about colouring: https://docs.adventure.kyori.net/minimessage/format.html#format
# Colors: hex &#RRGGBB and legacy & codes only (see .cursor/rules/locale-and-colors.mdc)
name: "English"
@ -236,10 +236,14 @@ lobby:
head:
material: ""
name: "&#00D4FF&l%s"
name: "&#FFFF55%s%s"
muted_suffix: " &#FF4444[mute]"
lore: ""
top: "&#404040Muted in lobby||"
bottom: "&#00D4FFLeft click&#404040 to kick from lobby||&#00D4FFRight click&#404040 to (un)mute in lobby"
lore_kick: "&#A0A0A0Left-click &#FFFFFFto kick"
lore_mute: "&#A0A0A0Right-click &#FFFFFFto mute"
lore_unmute: "&#A0A0A0Right-click &#FFFFFFto unmute"
scoreboard:
@ -256,8 +260,24 @@ scoreboard:
other:
no_do: "&#A0A0A0You can't do this right now."
no_menu_items: "&#FF4444&lNo menu items are available for you right now."
joining_disabled: "&#FF4444&lJoining is currently disabled."
no_teleporting: "&#FFFFFFYou can't enter the parkour world by teleporting!"
customize: "&#A0A0A0Customize your parkour experience by using \"/parkour\""
leave_error: "&#FF4444&lThere was an error while trying to leave."
bungee_kick: "Couldn't move you to %s. Please rejoin."
menu_unavailable: "&#FF4444%s &#A0A0A0(%d rows) — menu unavailable."
paged_menu_unavailable: "&#FF4444%s &#A0A0A0(%d rows, %d items) — menu unavailable."
back:
material: "red_dye"
name: "&#DE1F1F&lBack"
lore: ""
next:
material: "lime_dye"
name: "&#0DCB07&lNext"
lore: ""
quit:
material: "barrier"
@ -280,19 +300,95 @@ modes:
name: "&#00D4FF&lElytra"
lore: "&#A0A0A0Fly through rings!||&#A0A0A0Use fireworks to boost"
start_hint: "&#A0A0A0Fly through the rings! Use fireworks to boost."
ring_perfect: "&#55FF55Perfect! +%d"
ring_good: "&#55FF55Good! +%d"
fell: "&#FF4444Fell: %s"
action_bar: "&#FFFF55Ring: %d/%d &#A0A0A0| &#55FF55Score: &#FFFFFF%d"
race:
material: "golden_boots"
name: "&#FFD700&lRace"
lore: "&#A0A0A0First to reach target score!||&#A0A0A0Compete for fastest time"
completed: "&#FFD700Finished! Score: &#FFFFFF%s &#A0A0Ain &#FFFFFF%s"
title: "&#FFD700&lRACE COMPLETE!"
subtitle: "&#55FF55Finished in &#FFFFFF%s"
action_bar: "%s &#FFFF55%d &#A0A0A0/ &#55FF55%d%s"
action_bar_left: " &#A0A0A0(&#FF4444%d &#A0A0A0left)"
action_bar_done: " &#FFD700&lDONE!"
coop:
material: "player_head"
name: "&#00FF00&lCo-op"
lore: "&#A0A0A0Team up with friends!||&#A0A0A0Shared score and progress"
player_joined: "&#00FF00%s &#A0A0Ajoined the co-op session!"
stats: "&#A0A0ATeam score: &#FFFFFF%s &#A0A0A| Your blocks: &#FFFFFF%s &#A0A0A(&#FFFFFF%s%%&#A0A0A)"
player_joined: "&#00FF00%s &#A0A0A0joined the co-op session!"
stats: "&#A0A0A0Team score: &#FFFFFF%s &#A0A0A0| Your blocks: &#FFFFFF%s &#A0A0A0(&#FFFFFF%s%%&#A0A0A0)"
milestone_title: "&#FFD700&lTeam: &#FFFFFF%d"
milestone_subtitle: "&#55FF55Keep it up!"
admin:
cooldown: "<gray>Please wait before using this again."
cooldown: "&#A0A0A0Please wait before using this again."
force_join_everyone: "&#55FF55Force joined everyone!"
force_join_player: "&#55FF55Force joined %s!"
force_leave_everyone: "&#55FF55Force kicked everyone!"
player_not_online: "&#FF4444Player not online!"
player_not_playing: "&#FF4444That player isn't playing parkour!"
reset_all: "&#55FF55Reset all high scores."
reset_player: "&#55FF55Reset high score of %s."
recover_inventory: "&#55FF55Recovered inventory of %s."
recover_inventory_missing: "&#FF4444No saved inventory found for %s."
commands:
too_many_args: "&#A0A0A0Too many arguments. Try /parkour help"
error: "&#FF4444&lLoParkour: command error — see server console."
unknown_player: "&#FF4444Unknown player."
not_playing: "&#FF4444That player isn't playing."
reload: "&#A0A0A0Reloading config files... (locales load asynchronously)"
help:
header: "&#404040&m--------------- &#FF6464&lLoParkour &#404040&m---------------"
line_parkour: "&#A0A0A0/parkour &#404040- Main command"
line_join: "&#A0A0A0/parkour join [mode/player] &#404040- Join a mode"
line_leave: "&#A0A0A0/parkour leave &#404040- Leave parkour"
line_menu: "&#A0A0A0/parkour menu &#404040- Open the menu"
line_play: "&#A0A0A0/parkour play &#404040- Mode selection menu"
line_leaderboard: "&#A0A0A0/parkour leaderboard [type] &#404040- Leaderboard"
line_schematic: "&#A0A0A0/LoParkour schematic &#404040- Schematic tools"
line_reload: "&#A0A0A0/LoParkour reload &#404040- Reload configs"
line_reset: "&#A0A0A0/LoParkour reset <everyone/player> &#404040- Reset scores"
line_force: "&#A0A0A0/LoParkour forcejoin/forceleave <target> &#404040- Force join/leave"
line_recover: "&#A0A0A0/LoParkour recoverinventory <player> &#404040- Recover inventory"
schematic:
wand:
material: "golden_axe"
name: "&#FF4444&lSchematic Wand"
lore: "&#A0A0A0Left click: position 1||&#A0A0A0Right click: position 2"
help:
header: "&#8B0000&lSchematics &#404040-----------"
line_wand: "&#FF4444/LoParkour schematic wand &#404040- &#A0A0A0Selection wand"
line_pos1: "&#FF4444/LoParkour schematic pos1 &#404040- &#A0A0A0Set position 1"
line_pos2: "&#FF4444/LoParkour schematic pos2 &#404040- &#A0A0A0Set position 2"
line_create: "&#FF4444/LoParkour create <difficulty> &#404040- &#A0A0A0Save selection to .schem + yml"
line_create_named: "&#FF4444/LoParkour schematic create <name> <difficulty> &#404040- &#A0A0A0Named save"
line_convert: "&#FF4444/LoParkour schematic convert &#404040- &#A0A0A0Convert .lpschem to .schem"
line_paste: "&#FF4444/LoParkour schematic paste <name> &#404040- &#A0A0A0Test-paste"
line_list: "&#FF4444/LoParkour schematic list &#404040- &#A0A0A0List loaded"
line_reload: "&#FF4444/LoParkour schematic reload &#404040- &#A0A0A0Reload from disk"
line_folder: "&#A0A0A0Folder: plugins/LoParkour/schematics/ (.nbt, .schem, .schematic)"
line_docs: "&#A0A0A0Docs: plugins/LoParkour/docs/SCHEMATICS.md (also in jar /docs)"
wand_given: "&#A0A0A0Wand given. Left = pos1, Right = pos2."
list_header: "&#A0A0A0Loaded schematics:"
list_entry: "&#A0A0A0- &#FF4444%s &#404040(%s, %s)"
reloaded: "&#A0A0A0Reloaded."
pos_set: "&#A0A0A0Position %s set to %s"
invalid_name: "&#FF4444Invalid name. Use only letters, numbers, hyphens and underscores."
invalid_difficulty: "&#FF4444Difficulty must be a number from 0.0 to 1.0 (e.g. 0.25, 0.5, 1.0)."
selection_incomplete: "&#FF4444Selection incomplete. Use wand or pos1/pos2 first."
created: "&#A0A0A0Added &#FF4444%s &#404040(diff %s) as &#FF4444%s"
create_failed: "&#FF4444Create failed: %s"
convert_done: "&#A0A0A0Converted %s file(s), %s failed."
convert_truncated: "&#A0A0A0...and %s more (see console)."
not_found: "&#FF4444Schematic '%s' not found."
pasted: "&#A0A0A0Pasted &#FF4444%s&#A0A0A0."
ghost:
saved: "&#00D4FFYour ghost was saved with a score of %s!"

View file

@ -1,7 +1,7 @@
# Русский (Russian, ru)
# By loki
# Материалы должны быть из: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Material.html
# Узнайте больше о цветах: https://docs.adventure.kyori.net/minimessage/format.html#format
# Цвета: hex &#RRGGBB и legacy & (см. .cursor/rules/locale-and-colors.mdc)
name: "Русский"
@ -235,10 +235,14 @@ lobby:
head:
material: ""
name: "&#00D4FF&l%s"
name: "&#FFFF55%s%s"
muted_suffix: " &#FF4444[заглушён]"
lore: ""
top: "&#404040Заглушён в лобби||"
bottom: "&#00D4FFЛевый клик&#404040 чтобы исключить из лобби||&#00D4FFПравый клик&#404040 чтобы заглушить/разблокировать в лобби"
lore_kick: "&#A0A0A0ЛКМ &#FFFFFF— исключить"
lore_mute: "&#A0A0A0ПКМ &#FFFFFF— заглушить"
lore_unmute: "&#A0A0A0ПКМ &#FFFFFF— разглушить"
scoreboard:
@ -255,8 +259,24 @@ scoreboard:
other:
no_do: "&#A0A0A0Вы не можете сделать это сейчас."
no_menu_items: "&#FF4444&lСейчас нет доступных пунктов меню."
joining_disabled: "&#FF4444&lПрисоединение временно отключено."
no_teleporting: "&#FFFFFFВы не можете войти в мир паркура телепортацией!"
customize: "&#A0A0A0Настройте свой паркур, используя \"/parkour\""
leave_error: "&#FF4444&lОшибка при выходе из паркура."
bungee_kick: "Не удалось перенести вас на %s. Перезайдите."
menu_unavailable: "&#FF4444%s &#A0A0A0(%d рядов) — меню недоступно."
paged_menu_unavailable: "&#FF4444%s &#A0A0A0(%d рядов, %d пунктов) — меню недоступно."
back:
material: "red_dye"
name: "&#DE1F1F&lНазад"
lore: ""
next:
material: "lime_dye"
name: "&#0DCB07&lДалее"
lore: ""
quit:
material: "barrier"
@ -279,22 +299,95 @@ modes:
name: "&#00D4FF&lЭлитры"
lore: "&#A0A0A0Летайте через кольца!||&#A0A0A0Используйте фейерверки для ускорения"
start_hint: "&#A0A0A0Летите через кольца! Используйте фейерверки для ускорения."
ring_perfect: "&#55FF55Идеально! +%d"
ring_good: "&#55FF55Хорошо! +%d"
fell: "&#FF4444Падение: %s"
action_bar: "&#FFFF55Кольцо: %d/%d &#A0A0A0| &#55FF55Счёт: &#FFFFFF%d"
race:
material: "golden_boots"
name: "&#FFD700&lГонка"
lore: "&#A0A0A0Первым достигните целевого счёта!||&#A0A0A0Соревнуйтесь за лучшее время"
completed: "&#FFD700Финиш! Счёт: &#FFFFFF%s &#A0A0Aза &#FFFFFF%s"
completed: "&#FFD700Финиш! Счёт: &#FFFFFF%s &#A0A0A0за &#FFFFFF%s"
title: "&#FFD700&lГОНКА ЗАВЕРШЕНА!"
subtitle: "&#55FF55Время: &#FFFFFF%s"
action_bar: "%s &#FFFF55%d &#A0A0A0/ &#55FF55%d%s"
action_bar_left: " &#A0A0A0(&#FF4444%d &#A0A0A0осталось)"
action_bar_done: " &#FFD700&lГОТОВО!"
coop:
material: "player_head"
name: "&#00FF00&lКооператив"
lore: "&#A0A0A0Объединитесь с друзьями!||&#A0A0A0Общий счёт и прогресс"
player_joined: "&#00FF00%s &#A0A0Aприсоединился к кооперативной сессии!"
stats: "&#A0A0AКомандный счёт: &#FFFFFF%s &#A0A0A| Ваши блоки: &#FFFFFF%s &#A0A0A(&#FFFFFF%s%%&#A0A0A)"
player_joined: "&#00FF00%s &#A0A0A0присоединился к кооперативной сессии!"
stats: "&#A0A0A0Командный счёт: &#FFFFFF%s &#A0A0A0| Ваши блоки: &#FFFFFF%s &#A0A0A0(&#FFFFFF%s%%&#A0A0A0)"
milestone_title: "&#FFD700&lКоманда: &#FFFFFF%d"
milestone_subtitle: "&#55FF55Так держать!"
admin:
cooldown: "<gray>Подождите немного перед повторным использованием."
cooldown: "&#A0A0A0Подождите перед повторным использованием."
force_join_everyone: "&#55FF55Все игроки принудительно вошли!"
force_join_player: "&#55FF55Игрок %s принудительно вошёл!"
force_leave_everyone: "&#55FF55Все игроки принудительно выгнаны!"
player_not_online: "&#FF4444Игрок не в сети!"
player_not_playing: "&#FF4444Игрок не в паркуре!"
reset_all: "&#55FF55Все рекорды сброшены."
reset_player: "&#55FF55Рекорд игрока %s сброшен."
recover_inventory: "&#55FF55Инвентарь %s восстановлен."
recover_inventory_missing: "&#FF4444Сохранённый инвентарь для %s не найден."
commands:
too_many_args: "&#A0A0A0Слишком много аргументов. Попробуйте /parkour help"
error: "&#FF4444&lLoParkour: ошибка команды — см. консоль сервера."
unknown_player: "&#FF4444Неизвестный игрок."
not_playing: "&#FF4444Этот игрок не в паркуре."
reload: "&#A0A0A0Перезагрузка конфигов... (локали грузятся асинхронно)"
help:
header: "&#404040&m--------------- &#FF6464&lLoParkour &#404040&m---------------"
line_parkour: "&#A0A0A0/parkour &#404040- Главная команда"
line_join: "&#A0A0A0/parkour join [режим/игрок] &#404040- Войти в режим"
line_leave: "&#A0A0A0/parkour leave &#404040- Выйти"
line_menu: "&#A0A0A0/parkour menu &#404040- Открыть меню"
line_play: "&#A0A0A0/parkour play &#404040- Выбор режима"
line_leaderboard: "&#A0A0A0/parkour leaderboard [тип] &#404040- Таблица лидеров"
line_schematic: "&#A0A0A0/LoParkour schematic &#404040- Схематики"
line_reload: "&#A0A0A0/LoParkour reload &#404040- Перезагрузка конфигов"
line_reset: "&#A0A0A0/LoParkour reset <все/игрок> &#404040- Сброс рекордов"
line_force: "&#A0A0A0/LoParkour forcejoin/forceleave <цель> &#404040- Принудительный вход/выход"
line_recover: "&#A0A0A0/LoParkour recoverinventory <игрок> &#404040- Восстановить инвентарь"
schematic:
wand:
material: "golden_axe"
name: "&#FF4444&lПалочка схем"
lore: "&#A0A0A0ЛКМ: позиция 1||&#A0A0A0ПКМ: позиция 2"
help:
header: "&#8B0000&lСхематики &#404040-----------"
line_wand: "&#FF4444/LoParkour schematic wand &#404040- &#A0A0A0Палочка выделения"
line_pos1: "&#FF4444/LoParkour schematic pos1 &#404040- &#A0A0A0Позиция 1"
line_pos2: "&#FF4444/LoParkour schematic pos2 &#404040- &#A0A0A0Позиция 2"
line_create: "&#FF4444/LoParkour create <сложность> &#404040- &#A0A0A0Сохранить в .schem + yml"
line_create_named: "&#FF4444/LoParkour schematic create <имя> <сложность> &#404040- &#A0A0A0С именем"
line_convert: "&#FF4444/LoParkour schematic convert &#404040- &#A0A0A0.lpschem → .schem"
line_paste: "&#FF4444/LoParkour schematic paste <имя> &#404040- &#A0A0A0Тестовая вставка"
line_list: "&#FF4444/LoParkour schematic list &#404040- &#A0A0A0Список"
line_reload: "&#FF4444/LoParkour schematic reload &#404040- &#A0A0A0Перезагрузка"
line_folder: "&#A0A0A0Папка: plugins/LoParkour/schematics/ (.nbt, .schem)"
line_docs: "&#A0A0A0Документация: plugins/LoParkour/docs/SCHEMATICS.md"
wand_given: "&#A0A0A0Палочка выдана. ЛКМ = pos1, ПКМ = pos2."
list_header: "&#A0A0A0Загруженные схемы:"
list_entry: "&#A0A0A0- &#FF4444%s &#404040(%s, %s)"
reloaded: "&#A0A0A0Перезагружено."
pos_set: "&#A0A0A0Позиция %s: %s"
invalid_name: "&#FF4444Недопустимое имя. Только буквы, цифры, дефис и подчёркивание."
invalid_difficulty: "&#FF4444Сложность: число от 0.0 до 1.0 (напр. 0.25, 0.5, 1.0)."
selection_incomplete: "&#FF4444Сначала выделите область (палочка или pos1/pos2)."
created: "&#A0A0A0Добавлено &#FF4444%s &#404040(сложн. %s) → &#FF4444%s"
create_failed: "&#FF4444Ошибка: %s"
convert_done: "&#A0A0A0Конвертировано: %s, ошибок: %s."
convert_truncated: "&#A0A0A0...ещё %s (см. консоль)."
not_found: "&#FF4444Схема '%s' не найдена."
pasted: "&#A0A0A0Вставлено: &#FF4444%s&#A0A0A0."
ghost:
saved: "&#00D4FFВаш призрак сохранён с результатом %s очков!"

View file

@ -1,7 +1,10 @@
# -------- Add your schematics here --------
# Add your schematic random code or custom name here, followed by the difficulty.
# If your file is named parkour-d0f227d2, then you can either rename the file to parkour-4 ('4: 0.5') or
# just add 'd0f227d2: 0.5'. Any questions? Join the Discord.
# -------- Structure difficulty --------
# plugins/LoParkour/schematics/
# .nbt — structure block export
# .schem / .schematic — WorldEdit or /lp create
# .lpschem — legacy; run /lp schematic convert
# In-game: /lp schematic wand → select → /lp create <difficulty>
# Docs: plugins/LoParkour/docs/SCHEMATICS.md
# -------- The difficulty of schematics --------
# These are represented by a number below or equal to 1.0.

View file

@ -0,0 +1,19 @@
-- Создание таблицы для адаптивных метрик
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;
-- Миграция существующих данных (если есть старая таблица)
INSERT INTO loparkour_player_stats (player_uuid, sessions_count, last_updated)
SELECT player_uuid, COUNT(*) as sessions, MAX(timestamp) as last_updated
FROM loparkour_scores
GROUP BY player_uuid
ON DUPLICATE KEY UPDATE sessions_count = VALUES(sessions_count);