diff --git a/TODO.md b/TODO.md index c3e5dcb..05b04b7 100644 --- a/TODO.md +++ b/TODO.md @@ -55,7 +55,7 @@ - **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. +- **30. SQL: настройки игрока не сохраняются.** ✅ **СДЕЛАНО**: `SQLDataMapper.serializeSettings` пишет `PlayerSettingsManager.toSettingsMap(player)` (12 ключей) в JSON-колонку, `deserializeSettings` читает её тем же словарём (`normalizeNumericSettings` + `applySettings`) и падает на `applyDefaults` при пустоте/битости → `schematicDifficulty`/`blockLead` больше не null при входе (NPE в `updateGeneratorSettings`/`PAPIHook` не стало). Диск и SQL теперь используют **один и тот же** набор ключей. - **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) → курс зигзагом и не выпрямляется. → При диагонали аппендикс не перезаписывать случайным; корректный поворот-домножитель. @@ -86,12 +86,14 @@ - **47. `UserRegistry.getUsers()` строит полный Set на каждый BlockBreak/Place/Drop/Damage** (UserRegistry.java:94-98) → O(n) аллокаций на горячем пути. → Кэш `Map` + привязка через 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. +- **168. `ParkourUser.chatType` — состояние без записи.** Поле инициализировано `PUBLIC`, читается в `ParkourRestrictionListener.onChat` (switch `chatType()`), но сеттера нет нигде → ветки PRIVATE/GUILD недостижимы, режим группового чата не работает. → Либо подключить переключатель (команда/меню), либо убрать switch и мёртвый enum. ### Modes - **50. `GravityShiftMode` не снимает эффекты при выходе игрока** (только в reset для присутствующих, GravityShiftMode.java:149-163). → Снимать в unregister. - **51. `RaceMode.finishRace` — `reset(false)` оставляет игрока в воздухе** (RaceMode.java:142-147; GeneratorCleanup.reset чистит курс без регенерации/телепорта). → Телепорт в лобби + регенерация старта. - **52. `SpeedrunMode` оставляет удалённые блоки в `history`** → ломает координатные поиски. → Удалять координату из history при снятии блока. +- **167. Elytra-под-режимы не отличаются от базового.** `ElytraGenerator.getMode()` возвращает `Modes.elytra()`, а 5 под-генераторов (`Close/MinSpeed/TimeTrial/SpeedDemon/ObstacleGenerator`) его не переопределяют → guard `getMode() instanceof CloseMode` в `create()` (строка 47 во всех 5 режимах) **никогда не истинен**: повторная команда в том же режиме не делает early-return, а пересоздаёт сессию. Геттеры `Modes.speedDemon()/minSpeed()/timeTrial()/close()/obstacle()` не вызываются нигде (5 статиков только пишутся). → Переопределить `getMode()` в под-генераторах (или сравнивать класс генератора) и использовать/удалить геттеры. ### Схематики (жизнь в игре) @@ -181,7 +183,7 @@ - **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` времени в строке. → Инкапсуляция. +- **107. Публичные mutable поля-государство**: ✅ **ЧАСТИЧНО** — `GeneratorState` (15 полей) и весь кластер `player/*` (`ParkourUser`, `ParkourPlayer`, `PlayerSettings`) инкапсулированы в ПРОГРЕСС-6, `VisibilityModifier` переведён в `error`. Остались `ParkourGenerator.session/island/profile/state/generatorOptions` (публичные final — правило их разрешает, но состояние чужое мутируется) и `Score` времени в строке. → Инкапсуляция. ## 🧪 Тесты @@ -306,7 +308,19 @@ Checkstyle отработал `0 errors` (error-уровневые legacy-нар - **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-блок). +- **138. [S] Перевести PMD из report-only в жёсткий гейт.** После зачистки убрать `isIgnoreFailures=true` в `build.gradle.kts` (pmd-блок). ✅ сделано в `b771cc1`. +- **169. [M] Починить `ReturnCount` (69 warning / 52 файла) — ПЕРЕСТРУКТУРИРОВАНИЕМ, не порогом.** Все 69 — это половина + правила про void (`maxForVoid` по умолчанию = 1 ⇒ запрещена любая guard-клауза `if (x) { return; }`); для value-методов + при `max=5` нарушений 0. Владелец запретил поднимать порог → в каждом из 69 void-методов убираем ранний `return` + (условие становится обычным `if`-блоком) либо выносим тело в helper-метод. Следить за `NestedIfDepth` (error, max=2) + и `NPathComplexity`. После обнуления — перевести `ReturnCount` в `error`. +- **170. [M] Починить `IllegalCatch` (23 warning / 18 файлов).** 19× `catch (Exception)`, 3× `catch (Throwable)`, + 1× `catch (RuntimeException)`. Там, где конкретика реальна, — сужать до `IOException`/`NumberFormatException`/ + `SQLException`/`YAMLException` и т. п. На истинных границах (диспетчер команд `LoParkourCommand:36`, `session-state:84`, + `LoParkour:128`) `Throwable` **обязан** стать `RuntimeException`: `Throwable` глотает `Error` (OOM, StackOverflow) — + это баг, а не стиль. Пограничные `catch (Exception)` в `onEnable/onDisable`, загрузчиках конфигов/схематчей и + `SchematicManager` — оставляем широкими осознанно (релоад плагина не должен ронять сервер), но каждый такой site + требует комментария «почему именно Exception»; после разбора правило остаётся в warning на незачищенном остатке. После зачистки п.132–137 — удалить соответствующие записи из `config/checkstyle/suppressions.xml` (супрессии остаются только для ещё незачищенных legacy-файлов). @@ -398,8 +412,9 @@ Checkstyle отработал `0 errors` (error-уровневые legacy-нар > `ClassFanOutComplexity` 30→12, `MagicNumber` и `FinalLocalVariable` **warning→error** (оба чистые!). > Добавлены: `ReturnCount` (max 5), `FinalClass`, `HiddenField`, `VisibilityModifier`, `NPathComplexity` (секция вернулась в явном виде). > - `severity="warning"` (build зелёный, но каждое нарушение видно в отчёте) — **бэклог дизайна**, правило: -> **счётчик обнулился → сразу переводить в error**. Сейчас: `VisibilityModifier` 163, `ReturnCount` 69, `HiddenField` 30, -> `IllegalCatch` 23, `ClassFanOutComplexity` 17, `NPathComplexity` 12, `ParameterNumber` 8, `MethodLength` 5, `CDAC` 2. +> **счётчик обнулился → сразу переводить в error**. Актуальный список — в ПРОГРЕСС-6 ниже +> (на шаге 0.5: `VisibilityModifier` 163, `ReturnCount` 69, `HiddenField` 30, `IllegalCatch` 23, +> `ClassFanOutComplexity` 17, `NPathComplexity` 12, `ParameterNumber` 8, `MethodLength` 5, `CDAC` 2). > - **`suppressions.xml` ОБНУЛЁН** (45 записей удалено, включая FileLength/NPath/WhitespaceAround/IllegalCatch). > > **Красный baseline (честный, до фиксов): 431 нарушение в 129 файлах.** Замеры порогов перед выбором: @@ -460,8 +475,80 @@ Checkstyle отработал `0 errors` (error-уровневые legacy-нар > в `ParkourRestrictionListener.onChat` unboxing `Boolean` → `getOrDefault(CHAT, true)` как в `ParkourOption.mayPerform`. > - **Счётчики:** checkstyle **0 error / 329 → 240 warning**; `VisibilityModifier` **163 → 75**, `MethodLength` 5 → 4. > В `config/options` — **0 нарушений VisibilityModifier** (было 88), остаются 2 `IllegalCatch`. -> `./gradlew check` зелёный: PMD 0, тесты 94/0 fail, JaCoCo/ArchUnit/Spotless ок, NullAway 48 (как в baseline). +> `./gradlew check` зелёный: PMD 0, тесты 94/0 fail, JaCoCo/ArchUnit/Spotless ок, NullAway 48 (как в baseline; +> ⚠️ как выяснилось на шаге 6 — это было артефактом обрезки javac на 100 предупреждениях, честные числа там же). > Пороги правил не снижались, `suppressions.xml` пустая. Реальных прогонов на сервере после правок не было (только `check`). +> +> ## ✅ ПРОГРЕСС-6 (Задача 2, шаг (c): инкапсуляция игрового состояния — `player/*` + `GeneratorState`, 2026-10-06) +> **Checkstyle 329 → 165 warning; `VisibilityModifier` 75 → 0 и переведён в `error`.** +> - **Кластер `player` закрыт:** `ParkourUser` (5 полей) и `ParkourPlayer` (12) → `private` + аксессоры без `get` (та же идиома, +> что в ПРОГРЕСС-5 для `config/options`). ~46 обращений в 19 файлах переписаны. +> - **Выключен баг тенимого `locale`:** `@Expose String locale` был объявлен и в `ParkourUser`, и в `ParkourPlayer` — +> запись в поле и в аксессор расходилась. Осталось одно поле в родителе; на gson-2.11.0 проверено, что `@Expose` +> наследуемого поля сериализуется, поэтому набор ключей на диске не изменился. +> - **П.107 для `GeneratorState`:** 15 публичных полей → `private` + аксессоры/сеттеры; инкременты и сбросы оформлены +> методами (`addGeneratedBlocks(int)`, `resetScore()`, `setScore(int)`), ~90 обращений в 25 файлах. +> Публичные `ParkourGenerator.session/island/profile/state` — как были (остаток п.107). +> - **SRP: настройки игрока вынесены в `player/data/PlayerSettings`** (11 полей + аксессоры). `ParkourPlayer` — +> 194 → 93 строки, засчитываемых методов 20 → 9. Это и починило свежесломанное нарушение PMD `TooManyMethods` (гейт 0 нарушений +> был испорчен моим же рефакторингом) — декомпозицией, а НЕ ослаблением порога и НЕ переименованием под name-based +> освобождение правила (`getX()` в проекте не используется: 0 объявлений в `src/main`). +> - **DRY по настройкам:** троекратное повторение санитизации «legacy-locale» → `PlayerSettingsManager.sanitizeLocale()` +> (фолбэк берётся из конфига, а не захардкожен `"en"`); байт-в-байт одинаковая сборка 12-ключевого map в +> `StorageDisk.readPlayer` и `SQLDataMapper.serializeSettings` → `toSettingsMap()`; удалены мёртвый `getColumnMappings()`, +> `PLAYER_COLUMNS` и дубль `OptionContainer` (п.106); реестр-фасады `ParkourPlayer.isPlayer/getPlayer/getPlayers` +> переехали в `UserRegistry` (22 файла); +> дубли `parseIntSafe`/`parseDoubleSafe` схлопнуты в `numericOrFallback`. +> - **Закрыт п.30** (SQL не сохранял настройки) и **формат disk-файла игрока пересобран сознательно:** раньше Gson писал +> `ParkourPlayer` по `@Expose`-полям, а читал его через Unsafe-аллокацию «полупустого» объекта, чей map потом переливали +> в живого игрока. Теперь `StorageDisk` пишет и читает тот же канонический `Map`, что и JSON-колонка SQL — +> один словарь на оба бэкенда, `@Expose` из доменных классов убран совсем. Старые файлы остаются читаемыми: 3 ключа, +> которые совпадали с именами полей (`particles`/`useSpecialBlocks`/`showFallMessage`), нормализует +> `StorageDisk.normalizeLegacyKeys()`. ⚠️ Живых файлов игроков в `run/plugins/LoParkour/players/` нет — миграция +> проверена кодом и тестами, не реальным апгрейдом. +> - **Артефакт замера предупреждений:** в `build.gradle.kts` (JavaCompile) добавлен `-Xmaxwarns 100000`, потому что javac по умолчанию +> обрывает список предупреждений на 100 — отсюда «NullAway 48» в ПРОГРЕСС-5. Честные замеры через `git stash`: +> baseline HEAD **NullAway 165 / Error Prone 289**, сейчас **NullAway 164 / EP 265** — регресса нет. +> - **Счётчики:** checkstyle **0 error / 165 warning** (бэклог: `ReturnCount` 69, `HiddenField` 30, `IllegalCatch` 23, +> `ClassFanOutComplexity` 17, `NPathComplexity` 12, `ParameterNumber` 8, `MethodLength` 4, `CDAC` 2). +> В ноль обнулены и переведены в `error`: `VisibilityModifier`, `EmptyLineSeparator`, `MissingDeprecated`. +> PMD **0** (жёсткий гейт), `./gradlew check` зелёный, тесты **94 / 0 fail (1 skipped)**. Пороги правил не снижались, +> `suppressions.xml` пустая. Реальных прогонов на сервере не было (только `check`). +> - **Найдено по пути и оформлено отдельными пунктами (молча не чинилось):** **167** (elytra-под-режимы недостижимы — +> `getMode()` не переопределён) и **168** (`ParkourUser.chatType` не записывается нигде). +> +> ## ✅ ПРОГРЕСС-7 (Задача 2, шаг (c): `HiddenField` обнулён + директива владельца про линтеры, 2026-10-06) +> **Checkstyle 165 → 135 warning; `HiddenField` 30 → 0 и переведён в `error`.** +> - **Директива владельца (зафиксирована, действует дальше):** нарушения линтеров **чинить**, а не **глушить** — +> пороги правил не поднимать, `suppressions.xml` не наполнять, под name-based освобождения не подстраиваться. +> В `error` правило переводится только после того, как счётчик реально обнулён правками. +> - **`HiddenField` 30 → 0** — тенящие параметры/локалы переименованы (поля не трогали): `Config` (12 методов-делегата, +> параметр `path` → `key`), `ObstacleGenerator` (внешнее поле `radius` → `obstacleRadius` — одним переименованием сняли +> 7 теней в enum-реализациях `getPoints(center, radius)`), `GhostService.setReplaying(uuid, active)`, +> `Leaderboard.sort(order)`, `PagedMenu.addToDisplay(menuItems)`, `Reward` (локал `string` → `command`), +> `ParticleData` (fluent-сеттеры принимают `value`), `World.create()` (локал `world` → `existingWorld`, а +> `World.world = world` превратился в обычное присваивание поля). +> - **`ReturnCount` 69 — решено оформить отдельным пунктом 169:** все 69 нарушений приходятся на половину правила +> про void (`maxForVoid` по умолчанию = 1, то есть запрещена любая guard-клауза `if (x) { return; }`); +> для value-методов при `max=5` нарушений **0**. Поднимать `maxForVoid` я предлагал, владелец отказал — значит +> 69 void-методов переструктурируются по-настоящему (early-return → `if`-блок или выделение helper'а), +> с оглядкой на `NestedIfDepth` (error, max=2) и `NPathComplexity`. +> - **`IllegalCatch` 23** — сузить типы там, где конкретика реальна (IO/NumberFormat/SQL/ClassNotFound), +> а на истинных границах заменить `catch (Throwable)` → `catch (RuntimeException)` (это не «приглушение», а починка: +> `Throwable` глотает `Error`-ы — OOM, StackOverflow). Диспетчер команд, `onEnable/onDisable`, загрузчики конфигов +> и схематчей, session-state — пограничные точки, их фиксируем отдельно (п.170). +> - **Авария и восстановление:** после частичного отката правок в IDE (отклонённые diffs) рабочий код был сломан: +> `player/data/PlayerSettings.java` удалился целиком, `ParkourPlayer` вернулся к 11 `@Expose`-полям при уже +> переписанных вызовах (`settings()` ссылался на несуществующее поле, `updateGeneratorSettings` пропал), +> в `GeneratorCleanup` потерялся импорт `@Nullable` (2 ошибки компиляции), `checkstyle.xml` откатился к +> HEAD-версии и **потерял все четыре флипа** (`VisibilityModifier`, `EmptyLineSeparator`, `MissingDeprecated`, +> `HiddenField`), `PagedMenu`/`GhostService` вернулись к тенящим параметрам, а с `ParkourUser` слез лишний `@Expose` +> (после перехода диска на map он мёртвый). Всё восстановлено и перепроверено: +> `./gradlew check` — **BUILD SUCCESSFUL**, checkstyle **0 error / 135 warning** (test-отчёт пуст), PMD **0**, +> тесты **94 / 0 fail (1 skipped)**, `suppressions.xml` пустая. Реальных прогонов на сервере по-прежнему не было. +> Урок на будущее: после любой отменённой пачки правок сверять `git status` + полный `check`, а не только счётчики. +> - **Остаток бэклога (135):** `ReturnCount` 69, `IllegalCatch` 23, `ClassFanOutComplexity` 17, `NPathComplexity` 12, +> `ParameterNumber` 8, `MethodLength` 4, `ClassDataAbstractionCoupling` 2. --- diff --git a/build.gradle.kts b/build.gradle.kts index 0b2e711..f7f0465 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -261,6 +261,7 @@ tasks.withType { tasks.withType { options.encoding = "UTF-8" options.release = javaRelease + options.compilerArgs.addAll(listOf("-Xmaxwarns", "100000")) options.errorprone { disableWarningsInGeneratedCode = true // NullAway: only code annotated @NullMarked-style packages is checked once adopted; diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index fa11a6b..fbc2357 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -113,12 +113,12 @@ - + - + @@ -182,15 +182,12 @@ - - - - + diff --git a/src/main/java/dev/loki/loparkour/LoParkour.java b/src/main/java/dev/loki/loparkour/LoParkour.java index 890f279..52a44c7 100644 --- a/src/main/java/dev/loki/loparkour/LoParkour.java +++ b/src/main/java/dev/loki/loparkour/LoParkour.java @@ -114,8 +114,8 @@ public final class LoParkour extends JavaPlugin { ParkourUser.leave(user); } - if (Modes.DEFAULT != null && Modes.DEFAULT.getLeaderboard() != null) { - Modes.DEFAULT.getLeaderboard().write(false); + if (Modes.defaultMode() != null && Modes.defaultMode().getLeaderboard() != null) { + Modes.defaultMode().getLeaderboard().write(false); } Rewards.clear(); diff --git a/src/main/java/dev/loki/loparkour/adaptive/storage/SQLStatsStorage.java b/src/main/java/dev/loki/loparkour/adaptive/storage/SQLStatsStorage.java index 74bc3d6..89d62fa 100644 --- a/src/main/java/dev/loki/loparkour/adaptive/storage/SQLStatsStorage.java +++ b/src/main/java/dev/loki/loparkour/adaptive/storage/SQLStatsStorage.java @@ -172,8 +172,8 @@ public class SQLStatsStorage implements StatsRepository { } private static final class PendingUpdate { - int jumps = 0; - int falls = 0; - int longestStreak = 0; + private int jumps = 0; + private int falls = 0; + private int longestStreak = 0; } } diff --git a/src/main/java/dev/loki/loparkour/api/core/ParkourAPI.java b/src/main/java/dev/loki/loparkour/api/core/ParkourAPI.java index fec21c3..c00c375 100644 --- a/src/main/java/dev/loki/loparkour/api/core/ParkourAPI.java +++ b/src/main/java/dev/loki/loparkour/api/core/ParkourAPI.java @@ -3,6 +3,7 @@ package dev.loki.loparkour.api.core; import dev.loki.loparkour.api.event.generation.ParkourBlockGenerateEvent; import dev.loki.loparkour.player.core.ParkourPlayer; import dev.loki.loparkour.player.core.ParkourUser; +import dev.loki.loparkour.player.service.UserRegistry; import org.bukkit.entity.Player; import org.jetbrains.annotations.Nullable; @@ -28,7 +29,7 @@ public final class ParkourAPI { * @return The {@link ParkourPlayer}. Null if not found. */ public static @Nullable ParkourPlayer getPlayer(Player player) { - return ParkourPlayer.getPlayer(player); + return UserRegistry.getPlayer(player); } /** diff --git a/src/main/java/dev/loki/loparkour/bootstrap/PluginBootstrap.java b/src/main/java/dev/loki/loparkour/bootstrap/PluginBootstrap.java index a1d3e5c..7cc9b25 100644 --- a/src/main/java/dev/loki/loparkour/bootstrap/PluginBootstrap.java +++ b/src/main/java/dev/loki/loparkour/bootstrap/PluginBootstrap.java @@ -142,7 +142,7 @@ public final class PluginBootstrap { private static void setupMetrics(@NotNull LoParkour plugin) { final Metrics metrics = new Metrics(plugin, BSTATS_PLUGIN_ID); metrics.addCustomChart(new SimplePie("using_sql", () -> Boolean.toString(OptionSQL.enabled()))); - metrics.addCustomChart(new SimplePie("using_rewards", () -> Boolean.toString(Rewards.REWARDS_ENABLED))); + metrics.addCustomChart(new SimplePie("using_rewards", () -> Boolean.toString(Rewards.enabled()))); metrics.addCustomChart(new SimplePie("locale_count", () -> Integer.toString(Locales.getLocaleCount()))); metrics.addCustomChart(new SingleLineChart("player_joins", () -> dev.loki.loparkour.player.service.UserRegistry.getJoinCount())); diff --git a/src/main/java/dev/loki/loparkour/command/admin/ops/ForceJoinExecutor.java b/src/main/java/dev/loki/loparkour/command/admin/ops/ForceJoinExecutor.java index ae87670..76b6ac0 100644 --- a/src/main/java/dev/loki/loparkour/command/admin/ops/ForceJoinExecutor.java +++ b/src/main/java/dev/loki/loparkour/command/admin/ops/ForceJoinExecutor.java @@ -36,7 +36,7 @@ public final class ForceJoinExecutor { } private void joinEveryone(CommandSender sender) { - Bukkit.getOnlinePlayers().forEach(p -> Modes.DEFAULT.create(p)); + Bukkit.getOnlinePlayers().forEach(p -> Modes.defaultMode().create(p)); AdminResponses.send(sender, LoParkour.PREFIX + Locales.getString(sender, "admin.force_join_everyone")); } @@ -45,7 +45,7 @@ public final class ForceJoinExecutor { if (closest == null) { return; } - Modes.DEFAULT.create(closest); + Modes.defaultMode().create(closest); AdminResponses.send(sender, LoParkour.PREFIX + Locales.getString(sender, "admin.force_join_player").formatted(closest.getName())); } @@ -55,6 +55,6 @@ public final class ForceJoinExecutor { AdminResponses.send(sender, LoParkour.PREFIX + Locales.getString(sender, "admin.player_not_online")); return; } - Modes.DEFAULT.create(other); + Modes.defaultMode().create(other); } } diff --git a/src/main/java/dev/loki/loparkour/command/admin/ops/ForceLeaveExecutor.java b/src/main/java/dev/loki/loparkour/command/admin/ops/ForceLeaveExecutor.java index 0443578..d6a510e 100644 --- a/src/main/java/dev/loki/loparkour/command/admin/ops/ForceLeaveExecutor.java +++ b/src/main/java/dev/loki/loparkour/command/admin/ops/ForceLeaveExecutor.java @@ -3,11 +3,11 @@ package dev.loki.loparkour.command.admin.ops; import dev.loki.loparkour.LoParkour; import dev.loki.loparkour.command.player.PlayerCommandHandler; import dev.loki.loparkour.config.locale.Locales; -import dev.loki.loparkour.player.core.ParkourPlayer; import dev.loki.loparkour.player.core.ParkourUser; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import dev.loki.loparkour.command.admin.support.AdminResponses; +import dev.loki.loparkour.player.service.UserRegistry; import org.bukkit.entity.Player; public final class ForceLeaveExecutor { @@ -32,7 +32,7 @@ public final class ForceLeaveExecutor { } private void leaveEveryone(CommandSender sender) { - ParkourPlayer.getPlayers().forEach(ParkourUser::leave); + UserRegistry.getPlayers().forEach(ParkourUser::leave); AdminResponses.send(sender, LoParkour.PREFIX + Locales.getString(sender, "admin.force_leave_everyone")); } diff --git a/src/main/java/dev/loki/loparkour/command/core/CommandTabCompleter.java b/src/main/java/dev/loki/loparkour/command/core/CommandTabCompleter.java index b3a570d..6729d86 100644 --- a/src/main/java/dev/loki/loparkour/command/core/CommandTabCompleter.java +++ b/src/main/java/dev/loki/loparkour/command/core/CommandTabCompleter.java @@ -3,8 +3,8 @@ package dev.loki.loparkour.command.core; import dev.loki.loparkour.LoParkour; import dev.loki.loparkour.api.core.Registry; import dev.loki.loparkour.menu.core.screen.ParkourOption; -import dev.loki.loparkour.player.core.ParkourPlayer; import dev.loki.loparkour.schematic.core.SchematicManager; +import dev.loki.loparkour.player.service.UserRegistry; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import org.jetbrains.annotations.NotNull; @@ -86,7 +86,7 @@ final class CommandTabCompleter { final List completions = new ArrayList<>(); if (sender.hasPermission(ParkourOption.ADMIN.permission)) { completions.add("everyone"); - ParkourPlayer.getPlayers().forEach(pp -> completions.add(pp.getName())); + UserRegistry.getPlayers().forEach(pp -> completions.add(pp.getName())); } return filter(input, completions); } @@ -95,7 +95,7 @@ final class CommandTabCompleter { final List completions = new ArrayList<>(); if (ParkourOption.JOIN.mayPerform(sender)) { Registry.getModes().forEach(m -> completions.add(m.getName())); - ParkourPlayer.getPlayers().forEach(pp -> completions.add(pp.getName())); + UserRegistry.getPlayers().forEach(pp -> completions.add(pp.getName())); } return filter(input, completions); } diff --git a/src/main/java/dev/loki/loparkour/command/player/JoinCommandExecutor.java b/src/main/java/dev/loki/loparkour/command/player/JoinCommandExecutor.java index 7cfa0aa..f12c8af 100644 --- a/src/main/java/dev/loki/loparkour/command/player/JoinCommandExecutor.java +++ b/src/main/java/dev/loki/loparkour/command/player/JoinCommandExecutor.java @@ -11,6 +11,7 @@ import dev.loki.loparkour.mode.base.MultiMode; import dev.loki.loparkour.player.core.ParkourPlayer; import dev.loki.loparkour.player.core.ParkourUser; import dev.loki.loparkour.session.core.Session; +import dev.loki.loparkour.player.service.UserRegistry; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -37,7 +38,7 @@ public class JoinCommandExecutor { CommandUtil.send(sender, LoParkour.PREFIX + Locales.getString(sender, "commands.unknown_player")); return; } - final ParkourPlayer pp = ParkourPlayer.getPlayer(other); + final ParkourPlayer pp = UserRegistry.getPlayer(other); if (pp == null) { CommandUtil.send(sender, LoParkour.PREFIX + Locales.getString(sender, "commands.not_playing")); return; @@ -53,16 +54,16 @@ public class JoinCommandExecutor { if (session.isAcceptingPlayers()) { joinAcceptingSession(p, session); } else { - Modes.SPECTATOR.create(p, session); + Modes.spectator().create(p, session); } } private void joinAcceptingSession(Player p, Session session) { - final Mode sessionMode = session.generator.getMode(); + final Mode sessionMode = session.generator().getMode(); if (sessionMode instanceof MultiMode mm) { mm.join(p, session); } else { - Modes.SPECTATOR.create(p, session); + Modes.spectator().create(p, session); } } } diff --git a/src/main/java/dev/loki/loparkour/command/player/PlayerCommandHandler.java b/src/main/java/dev/loki/loparkour/command/player/PlayerCommandHandler.java index daf75cd..b8902da 100644 --- a/src/main/java/dev/loki/loparkour/command/player/PlayerCommandHandler.java +++ b/src/main/java/dev/loki/loparkour/command/player/PlayerCommandHandler.java @@ -101,7 +101,7 @@ public class PlayerCommandHandler { if (ParkourUser.getUser(player) != null) { return; } - Modes.DEFAULT.create(player); + Modes.defaultMode().create(player); } private void handleReload(@NotNull CommandSender sender, @Nullable Player player) { diff --git a/src/main/java/dev/loki/loparkour/config/core/Config.java b/src/main/java/dev/loki/loparkour/config/core/Config.java index 8c93e32..64d52d1 100644 --- a/src/main/java/dev/loki/loparkour/config/core/Config.java +++ b/src/main/java/dev/loki/loparkour/config/core/Config.java @@ -18,10 +18,10 @@ public enum Config { REWARDS("rewards-v2.yml", List.of("score-rewards", "interval-rewards", "one-time-rewards")), SCHEMATICS("schematics/schematics.yml", List.of("difficulty")); - public File path; + private File path; public final String fileName; public final List ignoredSections; - public FileConfiguration fileConfiguration; + private FileConfiguration fileConfiguration; private ConfigAccessor accessor; @@ -31,6 +31,43 @@ public enum Config { this.path = null; // Resolved lazily } + /** + * Location of this config on disk. + * + * @return The file this configuration is read from. + */ + public File path() { + return path; + } + + /** + * Parsed contents of this config. + * + * @return The parsed configuration currently held in memory. + */ + public FileConfiguration fileConfiguration() { + return fileConfiguration; + } + + /** + * Remembers where this configuration lives on disk. Only called while the config files are first resolved. + * + * @param file The resolved config file. + */ + public void bindPath(@NotNull File file) { + this.path = file; + } + + /** + * Replaces the in-memory configuration and refreshes the accessor that delegates reads to it. + * + * @param configuration The freshly loaded configuration. + */ + public void apply(@NotNull FileConfiguration configuration) { + this.fileConfiguration = configuration; + this.accessor = new ConfigAccessor(configuration, fileName); + } + /** * Reload all configuration files. */ @@ -56,8 +93,7 @@ public enum Config { return; } - this.fileConfiguration = ConfigLoader.loadConfig(path); - this.accessor = new ConfigAccessor(fileConfiguration, fileName); + apply(ConfigLoader.loadConfig(path)); } /** @@ -71,55 +107,55 @@ public enum Config { // Delegate all accessor methods to ConfigAccessor - public boolean isPath(@NotNull String path) { - return accessor != null && accessor.isPath(path); + public boolean isPath(@NotNull String key) { + return accessor != null && accessor.isPath(key); } - public Object get(@NotNull String path) { - return accessor.get(path); + public Object get(@NotNull String key) { + return accessor.get(key); } - public boolean getBoolean(@NotNull String path) { - return accessor.getBoolean(path); + public boolean getBoolean(@NotNull String key) { + return accessor.getBoolean(key); } - public boolean getBoolean(@NotNull String path, boolean defaultValue) { - return accessor != null ? accessor.getBoolean(path, defaultValue) : defaultValue; + public boolean getBoolean(@NotNull String key, boolean defaultValue) { + return accessor != null ? accessor.getBoolean(key, defaultValue) : defaultValue; } - public int getInt(@NotNull String path) { - return accessor.getInt(path); + public int getInt(@NotNull String key) { + return accessor.getInt(key); } - public int getInt(@NotNull String path, int defaultValue) { - return accessor != null ? accessor.getInt(path, defaultValue) : defaultValue; + public int getInt(@NotNull String key, int defaultValue) { + return accessor != null ? accessor.getInt(key, defaultValue) : defaultValue; } - public double getDouble(@NotNull String path) { - return accessor.getDouble(path); + public double getDouble(@NotNull String key) { + return accessor.getDouble(key); } - public double getDouble(@NotNull String path, double defaultValue) { - return accessor != null ? accessor.getDouble(path, defaultValue) : defaultValue; + public double getDouble(@NotNull String key, double defaultValue) { + return accessor != null ? accessor.getDouble(key, defaultValue) : defaultValue; } @NotNull - public String getString(@NotNull String path) { - return accessor.getString(path); + public String getString(@NotNull String key) { + return accessor.getString(key); } @NotNull - public List getStringList(@NotNull String path) { - return accessor.getStringList(path); + public List getStringList(@NotNull String key) { + return accessor.getStringList(key); } @NotNull - public List getIntList(@NotNull String path) { - return accessor.getIntList(path); + public List getIntList(@NotNull String key) { + return accessor.getIntList(key); } @NotNull - public List getChildren(@NotNull String path, boolean... deep) { - return accessor.getChildren(path, deep); + public List getChildren(@NotNull String key, boolean... deep) { + return accessor.getChildren(key, deep); } } diff --git a/src/main/java/dev/loki/loparkour/config/core/ConfigLoader.java b/src/main/java/dev/loki/loparkour/config/core/ConfigLoader.java index d38fd41..b47639d 100644 --- a/src/main/java/dev/loki/loparkour/config/core/ConfigLoader.java +++ b/src/main/java/dev/loki/loparkour/config/core/ConfigLoader.java @@ -50,10 +50,10 @@ public final class ConfigLoader { public static void reloadAllConfigs(boolean initialLoad) { for (final Config config : Config.values()) { if (initialLoad) { - config.path = initializeConfigFile(config.fileName); - updateConfig(config.fileName, config.path, config.ignoredSections); + config.bindPath(initializeConfigFile(config.fileName)); + updateConfig(config.fileName, config.path(), config.ignoredSections); } - config.fileConfiguration = loadConfig(config.path); + config.load(); } validateConfigs(); diff --git a/src/main/java/dev/loki/loparkour/config/locale/Locales.java b/src/main/java/dev/loki/loparkour/config/locale/Locales.java index 816f0ee..8122e6d 100644 --- a/src/main/java/dev/loki/loparkour/config/locale/Locales.java +++ b/src/main/java/dev/loki/loparkour/config/locale/Locales.java @@ -170,13 +170,13 @@ public final class Locales { @NotNull private static String getPlayerLocale(@NotNull Player player) { final ParkourUser user = ParkourUser.getUser(player); - String loc = user == null ? OptionGeneral.optionsDefaults().get(ParkourOption.LANG) : user.locale; + String loc = user == null ? OptionGeneral.optionsDefaults().get(ParkourOption.LANG) : user.locale(); // Fix legacy Boolean "true" values from old config parsing if ("true".equals(loc) || "false".equals(loc)) { loc = OptionGeneral.optionsDefaults().get(ParkourOption.LANG); if (user != null) { - user.locale = loc; // Update user's locale to fixed value + user.setLocale(loc); // Update user's locale to fixed value } } diff --git a/src/main/java/dev/loki/loparkour/config/options/Option.java b/src/main/java/dev/loki/loparkour/config/options/Option.java index be9ff02..b5c09e6 100644 --- a/src/main/java/dev/loki/loparkour/config/options/Option.java +++ b/src/main/java/dev/loki/loparkour/config/options/Option.java @@ -28,7 +28,7 @@ public final class Option { OptionGeneral.init(firstLoad); OptionGeneral.initOptions(); - OptionStyles.initStyles("styles.list", Config.CONFIG.fileConfiguration, RandomStyle::new) + OptionStyles.initStyles("styles.list", Config.CONFIG.fileConfiguration(), RandomStyle::new) .forEach(Registry::register); } } diff --git a/src/main/java/dev/loki/loparkour/config/options/OptionGeneral.java b/src/main/java/dev/loki/loparkour/config/options/OptionGeneral.java index 1361454..57659fa 100644 --- a/src/main/java/dev/loki/loparkour/config/options/OptionGeneral.java +++ b/src/main/java/dev/loki/loparkour/config/options/OptionGeneral.java @@ -124,7 +124,7 @@ public final class OptionGeneral { if (option == ParkourOption.LANG) { LoParkour.getPlugin().getLogger().warning("LANG default path not found: " + defaultPath); LoParkour.getPlugin().getLogger().warning( - "Available keys: " + Config.CONFIG.fileConfiguration.getKeys(true)); + "Available keys: " + Config.CONFIG.fileConfiguration().getKeys(true)); } } diff --git a/src/main/java/dev/loki/loparkour/duels/DuelsCountdown.java b/src/main/java/dev/loki/loparkour/duels/DuelsCountdown.java index b53700f..be1c3a8 100644 --- a/src/main/java/dev/loki/loparkour/duels/DuelsCountdown.java +++ b/src/main/java/dev/loki/loparkour/duels/DuelsCountdown.java @@ -44,7 +44,7 @@ final class DuelsCountdown { void giveStartItem(@NotNull ParkourPlayer owner) { Bukkit.getScheduler().runTaskLater(LoParkour.getPlugin(), () -> { if (owner.player.isOnline()) { - owner.player.getInventory().addItem(Locales.getItem(owner.locale, "play.multi.duels.start").build()); + owner.player.getInventory().addItem(Locales.getItem(owner.locale(), "play.multi.duels.start").build()); } }, BANNER_DELAY_TICKS); } @@ -64,7 +64,7 @@ final class DuelsCountdown { } void tick(@NotNull int[] counter) { - if (generator.state.stopped) { + if (generator.state.stopped()) { cancel(); return; } @@ -79,19 +79,19 @@ final class DuelsCountdown { } void startRace() { - generator.allowJoining = false; + generator.setAllowJoining(false); for (final Map.Entry entry : generator.playerGenerators.entrySet()) { final Location ps = generator.playerSpawns.get(entry.getKey()); if (ps == null) { continue; } - final Vector heading = entry.getValue().state.heading; + final Vector heading = entry.getValue().state.heading(); final Location bs = ps.clone().add(FIRST_BLOCK_FORWARD * heading.getX() - SPAWN_CENTER_OFFSET, 0, FIRST_BLOCK_FORWARD * heading.getZ() - SPAWN_CENTER_OFFSET); entry.getValue().generateFirst(ps, bs); } for (final ParkourPlayer pp : new ArrayList<>(generator.playerGenerators.keySet())) { - pp.player.sendTitle(ColorUtil.color(Locales.getString(pp.locale, "play.multi.duels.go")), "", 0, TITLE_STAY, 0); + pp.player.sendTitle(ColorUtil.color(Locales.getString(pp.locale(), "play.multi.duels.go")), "", 0, TITLE_STAY, 0); } cancel(); } diff --git a/src/main/java/dev/loki/loparkour/duels/DuelsGenerator.java b/src/main/java/dev/loki/loparkour/duels/DuelsGenerator.java index b80b694..01de3a8 100644 --- a/src/main/java/dev/loki/loparkour/duels/DuelsGenerator.java +++ b/src/main/java/dev/loki/loparkour/duels/DuelsGenerator.java @@ -35,7 +35,7 @@ public class DuelsGenerator extends ParkourGenerator { private static final int VICTORY_STAY = 100; private static final int VICTORY_FADE_OUT = 10; - public boolean allowJoining = true; + private boolean allowJoining = true; public final ParkourPlayer owner; public final Map playerGenerators = new LinkedHashMap<>(); public final int goal; @@ -53,6 +53,22 @@ public class DuelsGenerator extends ParkourGenerator { countdown.giveStartItem(owner); } + /** + * @return True while new players may still join this duel. + */ + public boolean allowJoining() { + return allowJoining; + } + + /** + * Opens or closes the duel for new players. + * + * @param allowJoining Whether joining is allowed. + */ + public void setAllowJoining(boolean allowJoining) { + this.allowJoining = allowJoining; + } + @Override public void generateFirst(Location spawn, Location block) { playerSpawns.put(owner, spawn); @@ -65,7 +81,7 @@ public class DuelsGenerator extends ParkourGenerator { @Override public void tick() { - if (!state.stopped) { + if (!state.stopped()) { playerGenerators.values().forEach(SingleDuelsGenerator::tick); } } @@ -75,10 +91,10 @@ public class DuelsGenerator extends ParkourGenerator { return; } final SingleDuelsGenerator gen = new SingleDuelsGenerator(session); - gen.owningGenerator = this; + gen.setOwningGenerator(this); gen.setOwnedPlayer(pp); gen.setPlayerIndex(playerGenerators.size()); - gen.state.heading = OptionGeneral.heading().getDirection(); + gen.state.setHeading(OptionGeneral.heading().getDirection()); final Location spawnBase = session.getSpawnLocation(); if (spawnBase == null || spawnBase.getWorld() == null) { return; @@ -115,23 +131,23 @@ public class DuelsGenerator extends ParkourGenerator { } public void win(@NotNull ParkourPlayer winner) { - if (state.stopped) { + if (state.stopped()) { return; } - state.stopped = true; + state.setStopped(true); countdown.cancel(); final String winningName = winner.getName(); final String winningTime = playerGenerators.get(winner).getFormattedTime(); final List> leaderboard = new ArrayList<>(playerGenerators.entrySet()); - leaderboard.sort((one, two) -> two.getValue().state.score - one.getValue().state.score); + leaderboard.sort((one, two) -> two.getValue().state.score() - one.getValue().state.score()); for (final ParkourPlayer pp : new ArrayList<>(playerGenerators.keySet())) { - pp.send(Locales.getString(pp.locale, "play.multi.duels.overview").formatted(winningName, winningTime)); + pp.send(Locales.getString(pp.locale(), "play.multi.duels.overview").formatted(winningName, winningTime)); for (int i = 0; i < leaderboard.size(); i++) { - pp.send(Locales.getString(pp.locale, "play.multi.duels.place") - .formatted(i + 1, leaderboard.get(i).getKey().getName(), leaderboard.get(i).getValue().state.score)); + pp.send(Locales.getString(pp.locale(), "play.multi.duels.place") + .formatted(i + 1, leaderboard.get(i).getKey().getName(), leaderboard.get(i).getValue().state.score())); } - pp.player.sendTitle(ColorUtil.color(Locales.getString(pp.locale, + pp.player.sendTitle(ColorUtil.color(Locales.getString(pp.locale(), pp == winner ? "play.multi.duels.victory" : "play.multi.duels.loss")), "", VICTORY_FADE_IN, VICTORY_STAY, VICTORY_FADE_OUT); } Bukkit.getScheduler().runTaskLater(LoParkour.getPlugin(), () -> returnToLobby(), WIN_RETURN_DELAY_TICKS); @@ -155,13 +171,13 @@ public class DuelsGenerator extends ParkourGenerator { private void returnToLobby() { for (final ParkourPlayer pp : new ArrayList<>(playerGenerators.keySet())) { if (pp.player.isOnline() && playerGenerators.containsKey(pp)) { - Modes.DEFAULT.create(pp.player); + Modes.defaultMode().create(pp.player); } } } @Override public Mode getMode() { - return Modes.DUELS; + return Modes.duels(); } } diff --git a/src/main/java/dev/loki/loparkour/duels/DuelsMode.java b/src/main/java/dev/loki/loparkour/duels/DuelsMode.java index 4b09930..d57a20c 100644 --- a/src/main/java/dev/loki/loparkour/duels/DuelsMode.java +++ b/src/main/java/dev/loki/loparkour/duels/DuelsMode.java @@ -8,6 +8,7 @@ import dev.loki.loparkour.player.core.ParkourPlayer; import dev.loki.loparkour.player.core.ParkourUser; import dev.loki.loparkour.session.core.Session; import dev.loki.loparkour.util.item.Item; +import dev.loki.loparkour.player.service.UserRegistry; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -42,8 +43,8 @@ public class DuelsMode implements MultiMode { @Override public void create(Player player) { - final ParkourPlayer pp = ParkourPlayer.getPlayer(player); - if (pp != null && pp.session.generator instanceof DuelsGenerator) { + final ParkourPlayer pp = UserRegistry.getPlayer(player); + if (pp != null && pp.session.generator() instanceof DuelsGenerator) { return; } @@ -51,7 +52,7 @@ public class DuelsMode implements MultiMode { final int maxPlayers = getMaxPlayers(); Session.create(DuelsGenerator::new, - session -> session.getPlayers().size() < maxPlayers && ((DuelsGenerator) session.generator).allowJoining, + session -> session.getPlayers().size() < maxPlayers && ((DuelsGenerator) session.generator()).allowJoining(), null, player); } @@ -66,16 +67,16 @@ public class DuelsMode implements MultiMode { final ParkourPlayer pp = ParkourUser.register(player, session); session.addPlayers(pp); - ((DuelsGenerator) session.generator).addPlayer(pp); + ((DuelsGenerator) session.generator()).addPlayer(pp); } @Override public void leave(Player player, Session session) { - final ParkourPlayer pp = ParkourPlayer.getPlayer(player); + final ParkourPlayer pp = UserRegistry.getPlayer(player); if (pp == null || session.getPlayers().isEmpty()) { return; } - ((DuelsGenerator) session.generator).removePlayer(pp); + ((DuelsGenerator) session.generator()).removePlayer(pp); } } diff --git a/src/main/java/dev/loki/loparkour/duels/SingleDuelsGenerator.java b/src/main/java/dev/loki/loparkour/duels/SingleDuelsGenerator.java index 589efe7..3a65091 100644 --- a/src/main/java/dev/loki/loparkour/duels/SingleDuelsGenerator.java +++ b/src/main/java/dev/loki/loparkour/duels/SingleDuelsGenerator.java @@ -15,7 +15,7 @@ import java.util.List; * One parkour course per duels player, isolated from everyone else. * *

Scoring only looks at the owned player: {@link #getPlayers()} returns just - * the owned player, so {@code state.history}/{@code state.score} belong to that + * the owned player, so {@code state.history}/{@code state.score()} belong to that * single course. The owning {@link DuelsGenerator} ticks every instance.

*/ public class SingleDuelsGenerator extends ParkourGenerator { @@ -26,7 +26,7 @@ public class SingleDuelsGenerator extends ParkourGenerator { Material.LIGHT_GRAY_CONCRETE, Material.BLACK_CONCRETE, Material.GREEN_CONCRETE, Material.BROWN_CONCRETE, Material.CYAN_CONCRETE, Material.PURPLE_CONCRETE, Material.GRAY_CONCRETE, Material.PINK_CONCRETE); - public DuelsGenerator owningGenerator; + private DuelsGenerator owningGenerator; private ParkourPlayer ownedPlayer; private BlockData blockData; private boolean won; @@ -52,6 +52,10 @@ public class SingleDuelsGenerator extends ParkourGenerator { state.specialChances.clear(); } + public void setOwningGenerator(@NotNull DuelsGenerator generator) { + this.owningGenerator = generator; + } + public void setOwnedPlayer(@NotNull ParkourPlayer pp) { this.ownedPlayer = pp; } @@ -95,7 +99,7 @@ public class SingleDuelsGenerator extends ParkourGenerator { } private void checkWin() { - if (!won && owningGenerator != null && state.score >= owningGenerator.goal) { + if (!won && owningGenerator != null && state.score() >= owningGenerator.goal) { won = true; owningGenerator.win(ownedPlayer); } @@ -103,6 +107,6 @@ public class SingleDuelsGenerator extends ParkourGenerator { @Override public Mode getMode() { - return Modes.DUELS; + return Modes.duels(); } } diff --git a/src/main/java/dev/loki/loparkour/elytra/close/CloseMode.java b/src/main/java/dev/loki/loparkour/elytra/close/CloseMode.java index aecedb5..67ed77a 100644 --- a/src/main/java/dev/loki/loparkour/elytra/close/CloseMode.java +++ b/src/main/java/dev/loki/loparkour/elytra/close/CloseMode.java @@ -7,6 +7,7 @@ import dev.loki.loparkour.mode.base.ModeMessages; import dev.loki.loparkour.player.core.ParkourPlayer; import dev.loki.loparkour.session.core.Session; import dev.loki.loparkour.util.item.Item; +import dev.loki.loparkour.player.service.UserRegistry; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -42,8 +43,8 @@ public class CloseMode implements Mode { return; } - final ParkourPlayer pp = ParkourPlayer.getPlayer(player); - if (pp != null && pp.session.generator != null && pp.session.generator.getMode() instanceof CloseMode) { + final ParkourPlayer pp = UserRegistry.getPlayer(player); + if (pp != null && pp.session.generator() != null && pp.session.generator().getMode() instanceof CloseMode) { return; } diff --git a/src/main/java/dev/loki/loparkour/elytra/core/ElytraGenerator.java b/src/main/java/dev/loki/loparkour/elytra/core/ElytraGenerator.java index 894ae1a..767e88f 100644 --- a/src/main/java/dev/loki/loparkour/elytra/core/ElytraGenerator.java +++ b/src/main/java/dev/loki/loparkour/elytra/core/ElytraGenerator.java @@ -25,7 +25,7 @@ public class ElytraGenerator extends ParkourGenerator { private static final double GENERATE_DISTANCE = 6.0; public final Map sections = new LinkedHashMap<>(); public final ClientBlockChanger blockChanger = new ClientBlockChanger(); - protected double movementScore = 0.0; + private double movementScore = 0.0; private Long startTime = null; private Vector playerSpawn; private Vector blockSpawn; @@ -133,7 +133,7 @@ public class ElytraGenerator extends ParkourGenerator { @Override public Mode getMode() { - return Modes.ELYTRA; + return Modes.elytra(); } public int getElytraScore() { diff --git a/src/main/java/dev/loki/loparkour/elytra/core/ElytraMode.java b/src/main/java/dev/loki/loparkour/elytra/core/ElytraMode.java index 4d0c5f3..59c3785 100644 --- a/src/main/java/dev/loki/loparkour/elytra/core/ElytraMode.java +++ b/src/main/java/dev/loki/loparkour/elytra/core/ElytraMode.java @@ -7,6 +7,7 @@ import dev.loki.loparkour.mode.base.ModeMessages; import dev.loki.loparkour.player.core.ParkourPlayer; import dev.loki.loparkour.session.core.Session; import dev.loki.loparkour.util.item.Item; +import dev.loki.loparkour.player.service.UserRegistry; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -42,8 +43,8 @@ public class ElytraMode implements Mode { return; } - final ParkourPlayer pp = ParkourPlayer.getPlayer(player); - if (pp != null && pp.session.generator != null && pp.session.generator.getMode() instanceof ElytraMode) { + final ParkourPlayer pp = UserRegistry.getPlayer(player); + if (pp != null && pp.session.generator() != null && pp.session.generator().getMode() instanceof ElytraMode) { return; } diff --git a/src/main/java/dev/loki/loparkour/elytra/minspeed/MinSpeedMode.java b/src/main/java/dev/loki/loparkour/elytra/minspeed/MinSpeedMode.java index d5d814b..ef6f25d 100644 --- a/src/main/java/dev/loki/loparkour/elytra/minspeed/MinSpeedMode.java +++ b/src/main/java/dev/loki/loparkour/elytra/minspeed/MinSpeedMode.java @@ -7,6 +7,7 @@ import dev.loki.loparkour.mode.base.ModeMessages; import dev.loki.loparkour.player.core.ParkourPlayer; import dev.loki.loparkour.session.core.Session; import dev.loki.loparkour.util.item.Item; +import dev.loki.loparkour.player.service.UserRegistry; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -42,8 +43,8 @@ public class MinSpeedMode implements Mode { return; } - final ParkourPlayer pp = ParkourPlayer.getPlayer(player); - if (pp != null && pp.session.generator != null && pp.session.generator.getMode() instanceof MinSpeedMode) { + final ParkourPlayer pp = UserRegistry.getPlayer(player); + if (pp != null && pp.session.generator() != null && pp.session.generator().getMode() instanceof MinSpeedMode) { return; } diff --git a/src/main/java/dev/loki/loparkour/elytra/obstacle/ObstacleGenerator.java b/src/main/java/dev/loki/loparkour/elytra/obstacle/ObstacleGenerator.java index 09989ea..c01e129 100644 --- a/src/main/java/dev/loki/loparkour/elytra/obstacle/ObstacleGenerator.java +++ b/src/main/java/dev/loki/loparkour/elytra/obstacle/ObstacleGenerator.java @@ -27,11 +27,11 @@ public class ObstacleGenerator extends ElytraGenerator { private static final int OBSTACLE_KNOT = 3; private final Map> obstacles = new HashMap<>(); - private final int radius; + private final int obstacleRadius; public ObstacleGenerator(Session session) { super(session); - radius = Config.GENERATION.getInt("elytra.radius", DEFAULT_RADIUS); + obstacleRadius = Config.GENERATION.getInt("elytra.radius", DEFAULT_RADIUS); } @Override @@ -48,7 +48,7 @@ public class ObstacleGenerator extends ElytraGenerator { private void generateObstacle(int idx, ElytraSection section, int knotIdx) { final Obstacle obstacle = Obstacle.values()[ThreadLocalRandom.current().nextInt(Obstacle.values().length)]; - final List points = obstacle.getPoints(section.getKnot(knotIdx), radius); + final List points = obstacle.getPoints(section.getKnot(knotIdx), obstacleRadius); if (points.isEmpty()) { return; } diff --git a/src/main/java/dev/loki/loparkour/elytra/obstacle/ObstacleMode.java b/src/main/java/dev/loki/loparkour/elytra/obstacle/ObstacleMode.java index 74d68af..06a464d 100644 --- a/src/main/java/dev/loki/loparkour/elytra/obstacle/ObstacleMode.java +++ b/src/main/java/dev/loki/loparkour/elytra/obstacle/ObstacleMode.java @@ -7,6 +7,7 @@ import dev.loki.loparkour.mode.base.ModeMessages; import dev.loki.loparkour.player.core.ParkourPlayer; import dev.loki.loparkour.session.core.Session; import dev.loki.loparkour.util.item.Item; +import dev.loki.loparkour.player.service.UserRegistry; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -42,8 +43,8 @@ public class ObstacleMode implements Mode { return; } - final ParkourPlayer pp = ParkourPlayer.getPlayer(player); - if (pp != null && pp.session.generator != null && pp.session.generator.getMode() instanceof ObstacleMode) { + final ParkourPlayer pp = UserRegistry.getPlayer(player); + if (pp != null && pp.session.generator() != null && pp.session.generator().getMode() instanceof ObstacleMode) { return; } diff --git a/src/main/java/dev/loki/loparkour/elytra/speeddemon/SpeedDemonMode.java b/src/main/java/dev/loki/loparkour/elytra/speeddemon/SpeedDemonMode.java index b8948e7..b96d9ba 100644 --- a/src/main/java/dev/loki/loparkour/elytra/speeddemon/SpeedDemonMode.java +++ b/src/main/java/dev/loki/loparkour/elytra/speeddemon/SpeedDemonMode.java @@ -7,6 +7,7 @@ import dev.loki.loparkour.mode.base.ModeMessages; import dev.loki.loparkour.player.core.ParkourPlayer; import dev.loki.loparkour.session.core.Session; import dev.loki.loparkour.util.item.Item; +import dev.loki.loparkour.player.service.UserRegistry; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -42,8 +43,8 @@ public class SpeedDemonMode implements Mode { return; } - final ParkourPlayer pp = ParkourPlayer.getPlayer(player); - if (pp != null && pp.session.generator != null && pp.session.generator.getMode() instanceof SpeedDemonMode) { + final ParkourPlayer pp = UserRegistry.getPlayer(player); + if (pp != null && pp.session.generator() != null && pp.session.generator().getMode() instanceof SpeedDemonMode) { return; } diff --git a/src/main/java/dev/loki/loparkour/elytra/timetrial/TimeTrialMode.java b/src/main/java/dev/loki/loparkour/elytra/timetrial/TimeTrialMode.java index 536da77..ad4aacf 100644 --- a/src/main/java/dev/loki/loparkour/elytra/timetrial/TimeTrialMode.java +++ b/src/main/java/dev/loki/loparkour/elytra/timetrial/TimeTrialMode.java @@ -7,6 +7,7 @@ import dev.loki.loparkour.mode.base.ModeMessages; import dev.loki.loparkour.player.core.ParkourPlayer; import dev.loki.loparkour.session.core.Session; import dev.loki.loparkour.util.item.Item; +import dev.loki.loparkour.player.service.UserRegistry; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -42,8 +43,8 @@ public class TimeTrialMode implements Mode { return; } - final ParkourPlayer pp = ParkourPlayer.getPlayer(player); - if (pp != null && pp.session.generator != null && pp.session.generator.getMode() instanceof TimeTrialMode) { + final ParkourPlayer pp = UserRegistry.getPlayer(player); + if (pp != null && pp.session.generator() != null && pp.session.generator().getMode() instanceof TimeTrialMode) { return; } diff --git a/src/main/java/dev/loki/loparkour/generator/core/coordinator/GeneratorStatistics.java b/src/main/java/dev/loki/loparkour/generator/core/coordinator/GeneratorStatistics.java index e242c0a..a85bc70 100644 --- a/src/main/java/dev/loki/loparkour/generator/core/coordinator/GeneratorStatistics.java +++ b/src/main/java/dev/loki/loparkour/generator/core/coordinator/GeneratorStatistics.java @@ -6,8 +6,10 @@ import dev.loki.loparkour.leaderboard.model.Score; import dev.loki.loparkour.mode.base.Mode; import dev.loki.loparkour.player.core.ParkourPlayer; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.time.Duration; +import java.time.Instant; /** * Manages generator statistics and time formatting. @@ -62,15 +64,15 @@ public class GeneratorStatistics { for (final ParkourPlayer player : generator.getPlayers()) { final String message; - if (generator.state.score > record) { + if (generator.state.score() > record) { // New record message = Locales.getString(player.player, "parkour.fall.new-record") - .replace("%score%", String.valueOf(generator.state.score)) + .replace("%score%", String.valueOf(generator.state.score())) .replace("%time%", getFormattedTime()); } else { // Regular fall message = Locales.getString(player.player, "parkour.fall.regular") - .replace("%score%", String.valueOf(generator.state.score)) + .replace("%score%", String.valueOf(generator.state.score())) .replace("%record%", String.valueOf(record)) .replace("%time%", getFormattedTime()); } @@ -81,11 +83,12 @@ public class GeneratorStatistics { @NotNull private String getTime(@NotNull String format) { - if (generator.state.start == null) { + final @Nullable Instant start = generator.state.start(); + if (start == null) { return "00:00.000"; } - final long elapsed = java.time.Duration.between(generator.state.start, java.time.Instant.now()).toMillis(); + final long elapsed = Duration.between(start, Instant.now()).toMillis(); final Duration d = Duration.ofMillis(elapsed); if ("HH:mm:ss.SSS".equals(format)) { diff --git a/src/main/java/dev/loki/loparkour/generator/core/coordinator/ParkourGenerator.java b/src/main/java/dev/loki/loparkour/generator/core/coordinator/ParkourGenerator.java index 836d3f7..10cd499 100644 --- a/src/main/java/dev/loki/loparkour/generator/core/coordinator/ParkourGenerator.java +++ b/src/main/java/dev/loki/loparkour/generator/core/coordinator/ParkourGenerator.java @@ -55,7 +55,7 @@ public class ParkourGenerator { if (selection == null || selection.length < 2) { throw new IllegalArgumentException("Invalid session zone: selection must contain at least 2 locations"); } - this.state.zone = selection; + this.state.setZone(selection); } // Initialize components @@ -132,7 +132,7 @@ public class ParkourGenerator { } public Mode getMode() { - return Modes.DEFAULT; + return Modes.defaultMode(); } @NotNull diff --git a/src/main/java/dev/loki/loparkour/generator/core/model/GeneratorState.java b/src/main/java/dev/loki/loparkour/generator/core/model/GeneratorState.java index 7260c00..d0eddd1 100644 --- a/src/main/java/dev/loki/loparkour/generator/core/model/GeneratorState.java +++ b/src/main/java/dev/loki/loparkour/generator/core/model/GeneratorState.java @@ -5,6 +5,7 @@ import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.block.data.BlockData; import org.bukkit.util.Vector; +import org.jetbrains.annotations.Nullable; import java.time.Instant; import java.util.ArrayList; @@ -14,36 +15,30 @@ import java.util.Map; /** * Holds all state data for parkour generation. - * No logic - just data and getters/setters. + * No logic - just data and accessors. */ public class GeneratorState { // Score state - public int score = 0; - public int totalScore = 0; - public int schematicCooldown; - public boolean stopped = false; + private int score; + private boolean stopped; - // Location state - public Location blockSpawn; - public Location playerSpawn; - public Location lastStandingPlayerLocation; - public Vector heading; - public Location[] zone; // [min, max] bounding box for generation area + // Location state — seeded before the first jump is calculated: the zone by ParkourGenerator, + // the heading by Island, the spawn by BlockPlacer, so readers treat all three as non-null. + private Location playerSpawn; + private Vector heading; + private Location[] zone; // [min, max] bounding box for generation area - // Time state - public Instant start; + // Time state — null until the first block is scored. + private @Nullable Instant start; // Block history — all access is main-thread-only (Bukkit scheduler runTimer), so no sync wrapper needed. // Iteration is safe without external synchronization as all mutations happen on the same thread. public final List history = new ArrayList<>(); - public int lastPositionIndexPlayer = -1; - public int generatedBlocks = 0; + private int generatedBlocks; - // Schematic state - public boolean deleteSchematic = false; - public boolean waitForSchematicCompletion = false; - public List schematicBlocks = new ArrayList<>(); + // Schematic state — null while no schematic is pasted. + private @Nullable List schematicBlocks; // Chance maps public final Map distanceChances = new HashMap<>(); @@ -51,21 +46,78 @@ public class GeneratorState { public final Map specialChances = new HashMap<>(); public final Map defaultChances = new HashMap<>(); - public GeneratorState() { } + public int score() { + return score; + } + public void setScore(int score) { + this.score = score; + } + + /** Zeroes the score and restarts the timer. */ public void resetScore() { score = 0; start = null; } - public void clearHistory() { - history.clear(); - lastPositionIndexPlayer = 0; + public boolean stopped() { + return stopped; } - public void resetSchematicState() { - waitForSchematicCompletion = false; - deleteSchematic = true; - schematicBlocks.clear(); + public void setStopped(boolean stopped) { + this.stopped = stopped; + } + + public Location playerSpawn() { + return playerSpawn; + } + + public void setPlayerSpawn(Location playerSpawn) { + this.playerSpawn = playerSpawn; + } + + public Vector heading() { + return heading; + } + + public void setHeading(Vector heading) { + this.heading = heading; + } + + public Location[] zone() { + return zone; + } + + public void setZone(Location[] zone) { + this.zone = zone; + } + + public @Nullable Instant start() { + return start; + } + + public void setStart(Instant start) { + this.start = start; + } + + public int generatedBlocks() { + return generatedBlocks; + } + + public void setGeneratedBlocks(int generatedBlocks) { + this.generatedBlocks = generatedBlocks; + } + + /** Counts blocks placed by one generation step (a schematic may place several at once). */ + public void addGeneratedBlocks(int count) { + generatedBlocks += count; + } + + public @Nullable List schematicBlocks() { + return schematicBlocks; + } + + public void setSchematicBlocks(@Nullable List schematicBlocks) { + this.schematicBlocks = schematicBlocks; } } diff --git a/src/main/java/dev/loki/loparkour/generator/core/model/Island.java b/src/main/java/dev/loki/loparkour/generator/core/model/Island.java index 18cf286..ebe68cf 100644 --- a/src/main/java/dev/loki/loparkour/generator/core/model/Island.java +++ b/src/main/java/dev/loki/loparkour/generator/core/model/Island.java @@ -11,7 +11,6 @@ import org.bukkit.Material; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; -import org.bukkit.util.Vector; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; @@ -30,7 +29,7 @@ public final class Island { public final Session session; - public List blocks; + private List blocks; public Island(@NotNull Session session) { this.session = session; @@ -50,10 +49,9 @@ public final class Island { final int hx = face.getModX(); final int hz = face.getModZ(); - if (session.generator.state.heading == null) { - session.generator.state.heading = face.getDirection(); + if (session.generator().state.heading() == null) { + session.generator().state.setHeading(face.getDirection()); } - final Vector h = session.generator.state.heading; String firstBlockStr = Config.GENERATION.getString("advanced.island.parkour.first-block-material"); if (firstBlockStr.isBlank()) { @@ -85,12 +83,12 @@ public final class Island { final Location parkourStart = world.getBlockAt(qx, qy, qz).getLocation(); - session.generator.generateFirst(ps, parkourStart); - session.generator.startTick(); + session.generator().generateFirst(ps, parkourStart); + session.generator().startTick(); session.getPlayers().forEach(pp -> pp.setup(ps)); if (LoParkour.getGhostService() != null) { - LoParkour.getGhostService().startRun(session.generator); + LoParkour.getGhostService().startRun(session.generator()); } this.blocks = placed; diff --git a/src/main/java/dev/loki/loparkour/generator/jump/calculation/JumpCalculator.java b/src/main/java/dev/loki/loparkour/generator/jump/calculation/JumpCalculator.java index a0807a8..76ba57a 100644 --- a/src/main/java/dev/loki/loparkour/generator/jump/calculation/JumpCalculator.java +++ b/src/main/java/dev/loki/loparkour/generator/jump/calculation/JumpCalculator.java @@ -51,16 +51,16 @@ public class JumpCalculator { */ @NotNull public Block calculateNextBlock(@NotNull Block current, int distance, int height) { - if (generator.state.zone == null || generator.state.zone.length < 2) { + if (generator.state.zone() == null || generator.state.zone().length < 2) { throw new IllegalStateException("Generator zone not initialized"); } final JumpDirector director = new JumpDirector( - BoundingBox.of(generator.state.zone[0], generator.state.zone[1]), + BoundingBox.of(generator.state.zone()[0], generator.state.zone()[1]), current.getLocation().toVector() ); - generator.state.heading = director.getRecommendedHeading(generator.state.heading); + generator.state.setHeading(director.getRecommendedHeading(generator.state.heading())); int resolvedHeight = director.getRecommendedHeight(height); final JumpConstraints constraints = calculateJumpConstraints(current); @@ -76,7 +76,7 @@ public class JumpCalculator { final double randomOffsetSd = Arrays.asList(generator.generatorOptions) .contains(GeneratorOption.REDUCE_RANDOM_BLOCK_SELECTION_ANGLE) ? RANDOM_OFFSET_SD : DEFAULT_RANDOM_OFFSET_SD; - final Location candidate = validateAndRetryJump(current.getLocation(), generator.state.heading, + final Location candidate = validateAndRetryJump(current.getLocation(), generator.state.heading(), resolvedDistance, resolvedHeight, ThreadLocalRandom.current(), randomOffsetSd); return candidate.getBlock(); } diff --git a/src/main/java/dev/loki/loparkour/generator/jump/placement/BlockPlacer.java b/src/main/java/dev/loki/loparkour/generator/jump/placement/BlockPlacer.java index e91f2d4..1977357 100644 --- a/src/main/java/dev/loki/loparkour/generator/jump/placement/BlockPlacer.java +++ b/src/main/java/dev/loki/loparkour/generator/jump/placement/BlockPlacer.java @@ -55,8 +55,8 @@ public class BlockPlacer { worldPlacer.place(block, blockData); generator.state.history.add(block); - generator.state.generatedBlocks++; - generator.state.playerSpawn = spawn; + generator.state.addGeneratedBlocks(1); + generator.state.setPlayerSpawn(spawn); } /** @@ -102,7 +102,7 @@ public class BlockPlacer { final Block selectedBlock = blocks.get(0); worldPlacer.place(selectedBlock, blockData); generator.state.history.add(selectedBlock); - generator.state.generatedBlocks++; + generator.state.addGeneratedBlocks(1); } private void placeSpecialBlock() { @@ -121,6 +121,6 @@ public class BlockPlacer { final Block selectedBlock = blocks.get(0); worldPlacer.place(selectedBlock, specialBlockData); generator.state.history.add(selectedBlock); - generator.state.generatedBlocks++; + generator.state.addGeneratedBlocks(1); } } diff --git a/src/main/java/dev/loki/loparkour/generator/jump/placement/BlockWorldPlacer.java b/src/main/java/dev/loki/loparkour/generator/jump/placement/BlockWorldPlacer.java index d60f893..30e2c6f 100644 --- a/src/main/java/dev/loki/loparkour/generator/jump/placement/BlockWorldPlacer.java +++ b/src/main/java/dev/loki/loparkour/generator/jump/placement/BlockWorldPlacer.java @@ -10,6 +10,9 @@ import org.bukkit.Sound; import org.bukkit.block.Block; import org.bukkit.block.data.BlockData; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; /** * Applies block data to the world and plays placement effects for all players. @@ -45,9 +48,11 @@ class BlockWorldPlacer { } void deleteSchematic() { - if (generator.state.schematicBlocks != null) { - generator.state.schematicBlocks.forEach(block -> block.setType(Material.AIR)); - generator.state.schematicBlocks.clear(); + final @Nullable List blocks = generator.state.schematicBlocks(); + if (blocks == null) { + return; } + blocks.forEach(block -> block.setType(Material.AIR)); + blocks.clear(); } } diff --git a/src/main/java/dev/loki/loparkour/generator/jump/placement/schematic/SchematicBlockPlacer.java b/src/main/java/dev/loki/loparkour/generator/jump/placement/schematic/SchematicBlockPlacer.java index fcf3bb8..4b85a9c 100644 --- a/src/main/java/dev/loki/loparkour/generator/jump/placement/schematic/SchematicBlockPlacer.java +++ b/src/main/java/dev/loki/loparkour/generator/jump/placement/schematic/SchematicBlockPlacer.java @@ -33,7 +33,7 @@ public class SchematicBlockPlacer { } final double maxDifficulty = generator.getPlayers().stream() - .mapToDouble(p -> p.schematicDifficulty) + .mapToDouble(p -> p.settings().schematicDifficulty()) .min() .orElse(1.0); @@ -54,8 +54,8 @@ public class SchematicBlockPlacer { } generator.state.history.addAll(schematicBlocks); - generator.state.generatedBlocks += schematicBlocks.size(); - generator.state.schematicBlocks = schematicBlocks; + generator.state.addGeneratedBlocks(schematicBlocks.size()); + generator.state.setSchematicBlocks(schematicBlocks); for (final ParkourPlayer player : generator.getPlayers()) { new ParkourSchematicGenerateEvent(schematic, generator, player).call(); diff --git a/src/main/java/dev/loki/loparkour/generator/lifecycle/loop/LifecycleEventManager.java b/src/main/java/dev/loki/loparkour/generator/lifecycle/loop/LifecycleEventManager.java index fe7bdae..18e480b 100644 --- a/src/main/java/dev/loki/loparkour/generator/lifecycle/loop/LifecycleEventManager.java +++ b/src/main/java/dev/loki/loparkour/generator/lifecycle/loop/LifecycleEventManager.java @@ -11,7 +11,6 @@ import dev.loki.loparkour.reward.core.Rewards; import org.bukkit.Bukkit; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -55,14 +54,14 @@ public class LifecycleEventManager { } private void checkRewards() { - if (!Rewards.REWARDS_ENABLED) { + if (!Rewards.enabled()) { return; } final Mode mode = generator.getMode(); for (final ParkourPlayer player : generator.getPlayers()) { - final int score = generator.state.score; + final int score = generator.state.score(); applyScoreRewards(player, mode, score); applyIntervalRewards(player, mode, score); applyOneTimeRewards(player, mode, score); @@ -70,16 +69,16 @@ public class LifecycleEventManager { } private void applyScoreRewards(@NotNull ParkourPlayer player, @NotNull Mode mode, int score) { - if (!Rewards.SCORE_REWARDS.containsKey(score)) { + if (!Rewards.scoreRewards().containsKey(score)) { return; } - for (final Reward reward : Rewards.SCORE_REWARDS.get(score)) { + for (final Reward reward : Rewards.scoreRewards().get(score)) { reward.execute(player, mode); } } private void applyIntervalRewards(@NotNull ParkourPlayer player, @NotNull Mode mode, int score) { - for (final Map.Entry> entry : Rewards.INTERVAL_REWARDS.entrySet()) { + for (final Map.Entry> entry : Rewards.intervalRewards().entrySet()) { if (score > 0 && score % entry.getKey() == 0) { for (final Reward reward : entry.getValue()) { reward.execute(player, mode); @@ -89,35 +88,32 @@ public class LifecycleEventManager { } private void applyOneTimeRewards(@NotNull ParkourPlayer player, @NotNull Mode mode, int score) { - if (!Rewards.ONE_TIME_REWARDS.containsKey(score)) { + if (!Rewards.oneTimeRewards().containsKey(score)) { return; } - if (player.collectedRewards == null) { - player.collectedRewards = new ArrayList<>(); - } final String rewardKey = String.valueOf(score); - if (player.collectedRewards.contains(rewardKey)) { + if (player.settings().collectedRewards().contains(rewardKey)) { return; } - for (final Reward reward : Rewards.ONE_TIME_REWARDS.get(score)) { + for (final Reward reward : Rewards.oneTimeRewards().get(score)) { reward.execute(player, mode); } - player.collectedRewards.add(rewardKey); + player.settings().collectedRewards().add(rewardKey); player.save(false); } private void processFall() { // Record statistics - final int score = generator.state.score; + final int score = generator.state.score(); final String time = generator.getFormattedTime(); final String difficulty = String.valueOf(generator.getDifficultyScore()); - generator.registerScore(time, difficulty, generator.state.score); + generator.registerScore(time, difficulty, generator.state.score()); // Teleport players back to spawn before reset for (final ParkourPlayer player : generator.getPlayers()) { - if (generator.state.playerSpawn != null) { - player.teleport(generator.state.playerSpawn); + if (generator.state.playerSpawn() != null) { + player.teleport(generator.state.playerSpawn()); } // Clear scored blocks so same coordinates can be re-scored after respawn player.clearScoredBlocks(); diff --git a/src/main/java/dev/loki/loparkour/generator/lifecycle/loop/LifecycleTickManager.java b/src/main/java/dev/loki/loparkour/generator/lifecycle/loop/LifecycleTickManager.java index 62cc885..0bf4743 100644 --- a/src/main/java/dev/loki/loparkour/generator/lifecycle/loop/LifecycleTickManager.java +++ b/src/main/java/dev/loki/loparkour/generator/lifecycle/loop/LifecycleTickManager.java @@ -68,12 +68,12 @@ public class LifecycleTickManager { * Check if any player fell below the threshold. */ private void checkPlayerFall() { - if (generator.state.playerSpawn == null) { + if (generator.state.playerSpawn() == null) { return; } final Block latest = generator.getLatest(); - final double referenceY = latest != null ? latest.getY() : generator.state.playerSpawn.getY(); + final double referenceY = latest != null ? latest.getY() : generator.state.playerSpawn().getY(); final double fallThreshold = referenceY - FALL_THRESHOLD_OFFSET; for (final var player : generator.getPlayers()) { @@ -92,8 +92,8 @@ public class LifecycleTickManager { } private void maintainBlockLead() { - final int currentBlocks = generator.state.generatedBlocks; - final int targetBlocks = generator.state.score + generationLead(); + final int currentBlocks = generator.state.generatedBlocks(); + final int targetBlocks = generator.state.score() + generationLead(); if (currentBlocks < targetBlocks) { final int needed = targetBlocks - currentBlocks; diff --git a/src/main/java/dev/loki/loparkour/generator/lifecycle/loop/LifecycleVisualUpdater.java b/src/main/java/dev/loki/loparkour/generator/lifecycle/loop/LifecycleVisualUpdater.java index 4192d0d..470da43 100644 --- a/src/main/java/dev/loki/loparkour/generator/lifecycle/loop/LifecycleVisualUpdater.java +++ b/src/main/java/dev/loki/loparkour/generator/lifecycle/loop/LifecycleVisualUpdater.java @@ -35,7 +35,7 @@ public class LifecycleVisualUpdater { private void updatePlayerVisuals(@NotNull ParkourPlayer player) { // Update scoreboard if enabled - if (player.showScoreboard != null && player.showScoreboard) { + if (player.settings().showScoreboard() != null && player.settings().showScoreboard()) { player.updateScoreboard(generator); } diff --git a/src/main/java/dev/loki/loparkour/generator/lifecycle/player/GeneratorCleanup.java b/src/main/java/dev/loki/loparkour/generator/lifecycle/player/GeneratorCleanup.java index 16b9567..72a1d6d 100644 --- a/src/main/java/dev/loki/loparkour/generator/lifecycle/player/GeneratorCleanup.java +++ b/src/main/java/dev/loki/loparkour/generator/lifecycle/player/GeneratorCleanup.java @@ -8,6 +8,7 @@ import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.HashSet; @@ -98,7 +99,7 @@ public class GeneratorCleanup { * The end block is kept in history so the landing player can score it. */ public void removeSchematicBlocks() { - final List schematicBlocks = generator.state.schematicBlocks; + final @Nullable List schematicBlocks = generator.state.schematicBlocks(); if (schematicBlocks == null || schematicBlocks.isEmpty()) { return; } @@ -115,7 +116,7 @@ public class GeneratorCleanup { } generator.state.history.removeIf(remove::contains); - generator.state.schematicBlocks = null; + generator.state.setSchematicBlocks(null); } /** @@ -127,9 +128,8 @@ public class GeneratorCleanup { // Reset state generator.state.history.clear(); - generator.state.generatedBlocks = 0; - generator.state.score = 0; - generator.state.start = null; + generator.state.setGeneratedBlocks(0); + generator.state.resetScore(); if (regenerate) { regenerateFirstBlock(); @@ -145,15 +145,16 @@ public class GeneratorCleanup { } private void clearSchematicBlocks() { - if (generator.state.schematicBlocks == null) { + final @Nullable List blocks = generator.state.schematicBlocks(); + if (blocks == null) { return; } - clearBlocks(generator.state.schematicBlocks); - generator.state.schematicBlocks.clear(); + clearBlocks(blocks); + blocks.clear(); } private void regenerateFirstBlock() { - final Location spawn = generator.state.playerSpawn; + final Location spawn = generator.state.playerSpawn(); if (spawn == null) { return; } diff --git a/src/main/java/dev/loki/loparkour/generator/lifecycle/player/PlayerInteractionHandler.java b/src/main/java/dev/loki/loparkour/generator/lifecycle/player/PlayerInteractionHandler.java index 4de0836..198b0fa 100644 --- a/src/main/java/dev/loki/loparkour/generator/lifecycle/player/PlayerInteractionHandler.java +++ b/src/main/java/dev/loki/loparkour/generator/lifecycle/player/PlayerInteractionHandler.java @@ -67,11 +67,11 @@ public class PlayerInteractionHandler { */ public void handleScore() { // Start the timer on the very first scored block - if (generator.state.start == null) { - generator.state.start = Instant.now(); + if (generator.state.start() == null) { + generator.state.setStart(Instant.now()); } - generator.state.score++; + generator.state.setScore(generator.state.score() + 1); eventManager.handleScore(); // Fire event listeners for adaptive system @@ -127,8 +127,8 @@ public class PlayerInteractionHandler { /** Check if block is any schematic block (not just the last one). */ private boolean isOnSchematicBlock(@NotNull Block block) { - return generator.state.schematicBlocks != null - && historyContains(generator.state.schematicBlocks, block); + final @Nullable List blocks = generator.state.schematicBlocks(); + return blocks != null && historyContains(blocks, block); } private void handleSchematicEndBlock() { @@ -139,7 +139,7 @@ public class PlayerInteractionHandler { } private boolean isSchematicEndBlock(@NotNull Block block) { - final List schematicBlocks = generator.state.schematicBlocks; + final @Nullable List schematicBlocks = generator.state.schematicBlocks(); if (schematicBlocks == null || schematicBlocks.isEmpty()) { return false; } diff --git a/src/main/java/dev/loki/loparkour/ghost/service/GhostService.java b/src/main/java/dev/loki/loparkour/ghost/service/GhostService.java index ff7c165..8ce036c 100644 --- a/src/main/java/dev/loki/loparkour/ghost/service/GhostService.java +++ b/src/main/java/dev/loki/loparkour/ghost/service/GhostService.java @@ -35,7 +35,7 @@ public class GhostService { private boolean recordingEnabled(ParkourPlayer pp) { return serverEnabled() && !replaying.contains(pp.getUUID()) - && Boolean.TRUE.equals(pp.ghostRecording); + && Boolean.TRUE.equals(pp.settings().ghostRecording()); } public void startRun(ParkourGenerator generator) { @@ -79,11 +79,11 @@ public class GhostService { return replaying.contains(uuid); } - public void setReplaying(UUID uuid, boolean replaying) { - if (replaying) { - this.replaying.add(uuid); + public void setReplaying(UUID uuid, boolean active) { + if (active) { + replaying.add(uuid); } else { - this.replaying.remove(uuid); + replaying.remove(uuid); } } diff --git a/src/main/java/dev/loki/loparkour/hook/papi/resolver/GlobalPlaceholderResolver.java b/src/main/java/dev/loki/loparkour/hook/papi/resolver/GlobalPlaceholderResolver.java index 3dae7f3..3940eb6 100644 --- a/src/main/java/dev/loki/loparkour/hook/papi/resolver/GlobalPlaceholderResolver.java +++ b/src/main/java/dev/loki/loparkour/hook/papi/resolver/GlobalPlaceholderResolver.java @@ -23,11 +23,11 @@ public class GlobalPlaceholderResolver { return LoParkour.getPlugin().getDescription().getVersion(); } case "leader", "record_player" -> { - final Score score = Modes.DEFAULT.getLeaderboard().getScoreAtRank(1); + final Score score = Modes.defaultMode().getLeaderboard().getScoreAtRank(1); return score != null ? score.name() : "?"; } case "leader_score", "record_score", "record" -> { - final Score score = Modes.DEFAULT.getLeaderboard().getScoreAtRank(1); + final Score score = Modes.defaultMode().getLeaderboard().getScoreAtRank(1); return score != null ? Integer.toString(score.score()) : "?"; } default -> { diff --git a/src/main/java/dev/loki/loparkour/hook/papi/resolver/PlayerPlaceholderResolver.java b/src/main/java/dev/loki/loparkour/hook/papi/resolver/PlayerPlaceholderResolver.java index d410a5f..060664f 100644 --- a/src/main/java/dev/loki/loparkour/hook/papi/resolver/PlayerPlaceholderResolver.java +++ b/src/main/java/dev/loki/loparkour/hook/papi/resolver/PlayerPlaceholderResolver.java @@ -21,13 +21,13 @@ public class PlayerPlaceholderResolver { private String resolveLeaderboard(Player player, String params) { switch (params) { case "rank" -> { - return Integer.toString(Modes.DEFAULT.getLeaderboard().getRank(player.getUniqueId())); + return Integer.toString(Modes.defaultMode().getLeaderboard().getRank(player.getUniqueId())); } case "highscore", "high_score" -> { - return Integer.toString(Modes.DEFAULT.getLeaderboard().get(player.getUniqueId()).score()); + return Integer.toString(Modes.defaultMode().getLeaderboard().get(player.getUniqueId()).score()); } case "high_score_time" -> { - return Modes.DEFAULT.getLeaderboard().get(player.getUniqueId()).time(); + return Modes.defaultMode().getLeaderboard().get(player.getUniqueId()).time(); } default -> { return null; @@ -38,7 +38,7 @@ public class PlayerPlaceholderResolver { private String resolveSession(Player player, String params) { final ParkourUser user = ParkourUser.getUser(player); final ParkourPlayer pp = extractParkourPlayer(user); - if (pp == null || pp.session.generator == null) { + if (pp == null || pp.session.generator() == null) { return null; } return resolveGenerator(pp, params); @@ -49,7 +49,7 @@ public class PlayerPlaceholderResolver { return (ParkourPlayer) user; } if (user instanceof ParkourSpectator) { - return ((ParkourSpectator) user).closest; + return ((ParkourSpectator) user).closest(); } return null; } @@ -57,28 +57,28 @@ public class PlayerPlaceholderResolver { private String resolveGenerator(ParkourPlayer pp, String params) { switch (params) { case "score", "current_score" -> { - return Integer.toString(pp.session.generator.state.score); + return Integer.toString(pp.session.generator().state.score()); } case "time", "current_time" -> { - return pp.session.generator.getFormattedTime(); + return pp.session.generator().getFormattedTime(); } case "blocklead", "lead" -> { - return Integer.toString(pp.blockLead); + return Integer.toString(pp.settings().blockLead()); } case "style" -> { - return pp.style; + return pp.settings().style(); } case "time_pref", "time_preference" -> { - return Integer.toString(pp.selectedTime); + return Integer.toString(pp.settings().selectedTime()); } case "scoreboard" -> { - return pp.showScoreboard.toString(); + return pp.settings().showScoreboard().toString(); } case "difficulty" -> { - return Double.toString(pp.schematicDifficulty); + return Double.toString(pp.settings().schematicDifficulty()); } case "difficulty_string" -> { - return formatters.parseDifficulty(pp.schematicDifficulty); + return formatters.parseDifficulty(pp.settings().schematicDifficulty()); } default -> { return resolveScoreUntil(pp, params); @@ -94,7 +94,7 @@ public class PlayerPlaceholderResolver { try { final int interval = Integer.parseInt(replaced); if (interval > 0) { - return Integer.toString(interval - (pp.session.generator.state.score % interval)); + return Integer.toString(interval - (pp.session.generator().state.score() % interval)); } return "0"; } catch (NumberFormatException e) { diff --git a/src/main/java/dev/loki/loparkour/hook/papi/resolver/ScoreRankResolver.java b/src/main/java/dev/loki/loparkour/hook/papi/resolver/ScoreRankResolver.java index c89aafa..97180ce 100644 --- a/src/main/java/dev/loki/loparkour/hook/papi/resolver/ScoreRankResolver.java +++ b/src/main/java/dev/loki/loparkour/hook/papi/resolver/ScoreRankResolver.java @@ -27,7 +27,7 @@ class ScoreRankResolver { final int rank = Integer.parseInt(matcher.group(2)); final String name = matcher.group(1); final Mode mode = Registry.getMode(name); - final Leaderboard leaderboard = mode != null ? mode.getLeaderboard() : Modes.DEFAULT.getLeaderboard(); + final Leaderboard leaderboard = mode != null ? mode.getLeaderboard() : Modes.defaultMode().getLeaderboard(); return resolveScore(leaderboard, rank, valueFunction); } catch (NumberFormatException e) { return "?"; @@ -37,7 +37,7 @@ class ScoreRankResolver { private String resolveGenericRank(String rankData, Function valueFunction) { try { final int rank = Integer.parseInt(rankData); - return resolveScore(Modes.DEFAULT.getLeaderboard(), rank, valueFunction); + return resolveScore(Modes.defaultMode().getLeaderboard(), rank, valueFunction); } catch (NumberFormatException e) { return "?"; } diff --git a/src/main/java/dev/loki/loparkour/leaderboard/core/Leaderboard.java b/src/main/java/dev/loki/loparkour/leaderboard/core/Leaderboard.java index 7e5653d..d4537da 100644 --- a/src/main/java/dev/loki/loparkour/leaderboard/core/Leaderboard.java +++ b/src/main/java/dev/loki/loparkour/leaderboard/core/Leaderboard.java @@ -136,12 +136,12 @@ public class Leaderboard { /** * Returns sorted copy of the score map. - * @param sort The sorting method + * @param order The sorting method * @return A sorted map of scores */ - public Map sort(LeaderboardSorter.Sort sort) { + public Map sort(LeaderboardSorter.Sort order) { synchronized (scores) { - return sorter.sort(scores, sort); + return sorter.sort(scores, order); } } @@ -149,8 +149,8 @@ public class Leaderboard { * @deprecated Use {@link #sort(LeaderboardSorter.Sort)} instead */ @Deprecated - public Map sort(Sort sort) { - return sort(sort.toSorterSort()); + public Map sort(Sort order) { + return sort(order.toSorterSort()); } // ── Backward compatibility ──────────────────────────────────────────────── diff --git a/src/main/java/dev/loki/loparkour/listener/gameplay/ParkourRestrictionListener.java b/src/main/java/dev/loki/loparkour/listener/gameplay/ParkourRestrictionListener.java index 5a4dafc..1339c8c 100644 --- a/src/main/java/dev/loki/loparkour/listener/gameplay/ParkourRestrictionListener.java +++ b/src/main/java/dev/loki/loparkour/listener/gameplay/ParkourRestrictionListener.java @@ -8,6 +8,7 @@ import dev.loki.loparkour.menu.core.screen.ParkourOption; import dev.loki.loparkour.player.core.ParkourPlayer; import dev.loki.loparkour.player.core.ParkourUser; import dev.loki.loparkour.session.core.Session; +import dev.loki.loparkour.player.service.UserRegistry; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.EventHandler; @@ -26,10 +27,7 @@ import org.bukkit.event.player.PlayerTeleportEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; -/** - * Handles hotbar item interactions, chat routing, focus mode, and - * general restrictions (drop/place/break/damage) for parkour players. - */ +/** Hotbar interactions, chat routing, focus mode and general restrictions for parkour players. */ public class ParkourRestrictionListener implements Listener { @EventHandler @@ -50,7 +48,7 @@ public class ParkourRestrictionListener implements Listener { } event.setCancelled(true); - switch (user.chatType) { + switch (user.chatType()) { case LOBBY_ONLY -> session.getUsers().forEach( u -> u.sendTranslated("settings.chat.formats.lobby", player.getName(), event.getMessage())); case PLAYERS_ONLY -> session.getPlayers().forEach( @@ -81,7 +79,7 @@ public class ParkourRestrictionListener implements Listener { @EventHandler(priority = EventPriority.HIGHEST) public void onInteract(PlayerInteractEvent event) { final Player player = event.getPlayer(); - final ParkourPlayer pp = ParkourPlayer.getPlayer(player); + final ParkourPlayer pp = UserRegistry.getPlayer(player); if (pp == null) { return; } diff --git a/src/main/java/dev/loki/loparkour/listener/player/PlayerConnectionListener.java b/src/main/java/dev/loki/loparkour/listener/player/PlayerConnectionListener.java index 33257c5..2669066 100644 --- a/src/main/java/dev/loki/loparkour/listener/player/PlayerConnectionListener.java +++ b/src/main/java/dev/loki/loparkour/listener/player/PlayerConnectionListener.java @@ -33,7 +33,7 @@ public class PlayerConnectionListener implements Listener { final Player player = event.getPlayer(); if (Config.CONFIG.getBoolean("bungeecord.enabled")) { - Modes.DEFAULT.create(player); + Modes.defaultMode().create(player); return; } diff --git a/src/main/java/dev/loki/loparkour/menu/core/LPMenu.java b/src/main/java/dev/loki/loparkour/menu/core/LPMenu.java index 0242808..08df8fb 100644 --- a/src/main/java/dev/loki/loparkour/menu/core/LPMenu.java +++ b/src/main/java/dev/loki/loparkour/menu/core/LPMenu.java @@ -26,7 +26,7 @@ public abstract class LPMenu { protected String locale(@NotNull Player player) { final var user = dev.loki.loparkour.player.core.ParkourUser.getUser(player); - final String loc = user != null ? user.locale : OptionGeneral.optionsDefaults().get(ParkourOption.LANG); + final String loc = user != null ? user.locale() : OptionGeneral.optionsDefaults().get(ParkourOption.LANG); return (loc != null && !loc.isBlank()) ? loc : "en"; } diff --git a/src/main/java/dev/loki/loparkour/menu/core/Menus.java b/src/main/java/dev/loki/loparkour/menu/core/Menus.java index c1d645f..45ab837 100644 --- a/src/main/java/dev/loki/loparkour/menu/core/Menus.java +++ b/src/main/java/dev/loki/loparkour/menu/core/Menus.java @@ -20,26 +20,26 @@ public final class Menus { } // main - public static MainMenu MAIN = new MainMenu(); + public static final MainMenu MAIN = new MainMenu(); // play - public static PlayMenu PLAY = new PlayMenu(); - public static SingleMenu SINGLE = new SingleMenu(); - public static SpectatorMenu SPECTATOR = new SpectatorMenu(); + public static final PlayMenu PLAY = new PlayMenu(); + public static final SingleMenu SINGLE = new SingleMenu(); + public static final SpectatorMenu SPECTATOR = new SpectatorMenu(); // community - public static CommunityMenu COMMUNITY = new CommunityMenu(); - public static LeaderboardsMenu LEADERBOARDS = new LeaderboardsMenu(); - public static SingleLeaderboardMenu SINGLE_LEADERBOARD = new SingleLeaderboardMenu(); + public static final CommunityMenu COMMUNITY = new CommunityMenu(); + public static final LeaderboardsMenu LEADERBOARDS = new LeaderboardsMenu(); + public static final SingleLeaderboardMenu SINGLE_LEADERBOARD = new SingleLeaderboardMenu(); // settings - public static SettingsMenu SETTINGS = new SettingsMenu(); - public static LangMenu LANG = new LangMenu(); - public static ParkourSettingsMenu PARKOUR_SETTINGS = new ParkourSettingsMenu(); + public static final SettingsMenu SETTINGS = new SettingsMenu(); + public static final LangMenu LANG = new LangMenu(); + public static final ParkourSettingsMenu PARKOUR_SETTINGS = new ParkourSettingsMenu(); // lobby - public static DuelsMenu DUELS = new DuelsMenu(); - public static LobbyMenu LOBBY = new LobbyMenu(); - public static PlayerManagementMenu PLAYER_MANAGEMENT = new PlayerManagementMenu(); + public static final DuelsMenu DUELS = new DuelsMenu(); + public static final LobbyMenu LOBBY = new LobbyMenu(); + public static final PlayerManagementMenu PLAYER_MANAGEMENT = new PlayerManagementMenu(); } diff --git a/src/main/java/dev/loki/loparkour/menu/gui/PagedMenu.java b/src/main/java/dev/loki/loparkour/menu/gui/PagedMenu.java index ccc7fc5..af194a8 100644 --- a/src/main/java/dev/loki/loparkour/menu/gui/PagedMenu.java +++ b/src/main/java/dev/loki/loparkour/menu/gui/PagedMenu.java @@ -19,8 +19,8 @@ public class PagedMenu { this.title = title; } - public PagedMenu addToDisplay(List items) { - this.items = items; + public PagedMenu addToDisplay(List menuItems) { + items = menuItems; return this; } diff --git a/src/main/java/dev/loki/loparkour/menu/lobby/DuelsMenu.java b/src/main/java/dev/loki/loparkour/menu/lobby/DuelsMenu.java index 3e1b091..d6d4e70 100644 --- a/src/main/java/dev/loki/loparkour/menu/lobby/DuelsMenu.java +++ b/src/main/java/dev/loki/loparkour/menu/lobby/DuelsMenu.java @@ -59,7 +59,7 @@ public class DuelsMenu extends LPMenu { final Session session = sessions.get(i); final int slot = FIRST_SLOT + (i - startIndex); gui = gui.setItem(slot, sessionItem(player, session), e -> { - Modes.DUELS.join(player, session); + Modes.duels().join(player, session); player.closeInventory(); }); } @@ -78,7 +78,7 @@ public class DuelsMenu extends LPMenu { } private List collectOpenSessions() { - final int maxPlayers = Modes.DUELS.getMaxPlayers(); + final int maxPlayers = Modes.duels().getMaxPlayers(); final List sessions = new ArrayList<>(); for (final Session session : Divider.SECTIONS.keySet()) { if (isOpenForJoining(session, maxPlayers)) { @@ -89,13 +89,13 @@ public class DuelsMenu extends LPMenu { } private boolean isOpenForJoining(Session session, int maxPlayers) { - return session.generator instanceof DuelsGenerator generator - && generator.allowJoining + return session.generator() instanceof DuelsGenerator generator + && generator.allowJoining() && session.getPlayers().size() < maxPlayers; } private ItemStack sessionItem(@NotNull Player player, @NotNull Session session) { - final DuelsGenerator generator = (DuelsGenerator) session.generator; + final DuelsGenerator generator = (DuelsGenerator) session.generator(); final ItemStack item = new ItemStack(Material.PLAYER_HEAD); applySkullOwner(player, item, generator.owner); diff --git a/src/main/java/dev/loki/loparkour/menu/lobby/LobbyMenu.java b/src/main/java/dev/loki/loparkour/menu/lobby/LobbyMenu.java index 9f8c3d1..06c2007 100644 --- a/src/main/java/dev/loki/loparkour/menu/lobby/LobbyMenu.java +++ b/src/main/java/dev/loki/loparkour/menu/lobby/LobbyMenu.java @@ -30,7 +30,7 @@ public class LobbyMenu extends LPMenu { return; } - final String locale = user.locale; + final String locale = user.locale(); final String title = Locales.getString(locale, "lobby.name"); final boolean isHost = !user.session.getPlayers().isEmpty() @@ -60,7 +60,7 @@ public class LobbyMenu extends LPMenu { } private ItemStack visibilityItem(ParkourUser user) { - final String locale = user.locale; + final String locale = user.locale(); final List values = Locales.getStringList(locale, "lobby.visibility.values"); final String label = switch (user.session.getVisibility()) { case PUBLIC -> values.size() > 2 ? values.get(2) : "Public"; diff --git a/src/main/java/dev/loki/loparkour/menu/lobby/PlayerManagementMenu.java b/src/main/java/dev/loki/loparkour/menu/lobby/PlayerManagementMenu.java index 4707b99..f15efb4 100644 --- a/src/main/java/dev/loki/loparkour/menu/lobby/PlayerManagementMenu.java +++ b/src/main/java/dev/loki/loparkour/menu/lobby/PlayerManagementMenu.java @@ -9,6 +9,7 @@ import dev.loki.loparkour.player.core.ParkourUser; import dev.loki.loparkour.session.core.Session; import dev.loki.loparkour.util.text.ColorUtil; import dev.loki.loparkour.menu.gui.inventory.InventoryGui; +import dev.loki.loparkour.player.service.UserRegistry; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.event.inventory.ClickType; @@ -29,13 +30,13 @@ public class PlayerManagementMenu extends LPMenu { @Override public void open(@NotNull Player player) { - final ParkourPlayer viewer = ParkourPlayer.getPlayer(player); + final ParkourPlayer viewer = UserRegistry.getPlayer(player); if (viewer == null) { return; } final var session = viewer.session; - final String locale = viewer.locale; + final String locale = viewer.locale(); final String title = Locales.getString(locale, "lobby.player_management.name"); final List others = new ArrayList<>(); @@ -102,7 +103,7 @@ public class PlayerManagementMenu extends LPMenu { @NotNull Session session, @NotNull ParkourUser other, @NotNull ClickType click) { if (click == ClickType.LEFT) { - Modes.DEFAULT.create(other.player); + Modes.defaultMode().create(other.player); other.sendTranslated("lobby.player_management.kicked"); viewer.sendTranslated("lobby.player_management.advice"); } else if (click == ClickType.RIGHT) { diff --git a/src/main/java/dev/loki/loparkour/menu/play/SpectatorMenu.java b/src/main/java/dev/loki/loparkour/menu/play/SpectatorMenu.java index 9436e84..6990ab6 100644 --- a/src/main/java/dev/loki/loparkour/menu/play/SpectatorMenu.java +++ b/src/main/java/dev/loki/loparkour/menu/play/SpectatorMenu.java @@ -48,7 +48,7 @@ public class SpectatorMenu extends LPMenu { for (int i = startIndex; i < endIndex; i++) { final Session session = sessions.get(i); final int slot = FIRST_SLOT + (i - startIndex); // Slots 10-16 - gui = gui.setItem(slot, items.get(i), e -> Modes.SPECTATOR.create(player, session)); + gui = gui.setItem(slot, items.get(i), e -> Modes.spectator().create(player, session)); } if (currentPage > 0) { diff --git a/src/main/java/dev/loki/loparkour/menu/settings/LangMenu.java b/src/main/java/dev/loki/loparkour/menu/settings/LangMenu.java index 9e1a073..6f93b0b 100644 --- a/src/main/java/dev/loki/loparkour/menu/settings/LangMenu.java +++ b/src/main/java/dev/loki/loparkour/menu/settings/LangMenu.java @@ -6,6 +6,7 @@ import dev.loki.loparkour.menu.core.Menus; import dev.loki.loparkour.player.core.ParkourPlayer; import dev.loki.loparkour.util.text.ColorUtil; import dev.loki.loparkour.menu.gui.inventory.InventoryGui; +import dev.loki.loparkour.player.service.UserRegistry; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; @@ -30,12 +31,12 @@ public class LangMenu extends LPMenu { @Override public void open(@NotNull Player player) { - final ParkourPlayer pp = ParkourPlayer.getPlayer(player); + final ParkourPlayer pp = UserRegistry.getPlayer(player); if (pp == null) { return; } - final String locale = pp.locale; + final String locale = pp.locale(); final String title = Locales.getString(locale, "settings.lang.name"); final List langs = new ArrayList<>(Locales.getLocaleKeys()); @@ -52,7 +53,7 @@ public class LangMenu extends LPMenu { item.setItemMeta(meta); } gui = gui.setItem(FIRST_SLOT + i, item, e -> { - pp.locale = lang; + pp.setLocale(lang); pp.updateHotbar(); // Update hotbar with new language Menus.SETTINGS.open(player); }); diff --git a/src/main/java/dev/loki/loparkour/menu/settings/ParkourSettingsMenu.java b/src/main/java/dev/loki/loparkour/menu/settings/ParkourSettingsMenu.java index 85a2d66..0aab963 100644 --- a/src/main/java/dev/loki/loparkour/menu/settings/ParkourSettingsMenu.java +++ b/src/main/java/dev/loki/loparkour/menu/settings/ParkourSettingsMenu.java @@ -10,6 +10,7 @@ import dev.loki.loparkour.player.core.ParkourPlayer; import dev.loki.loparkour.style.core.Style; import dev.loki.loparkour.util.text.ColorUtil; import dev.loki.loparkour.menu.gui.inventory.InventoryGui; +import dev.loki.loparkour.player.service.UserRegistry; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; @@ -47,27 +48,27 @@ public class ParkourSettingsMenu extends LPMenu { @Override public void open(@NotNull Player player) { - final ParkourPlayer pp = ParkourPlayer.getPlayer(player); + final ParkourPlayer pp = UserRegistry.getPlayer(player); if (pp == null) { return; } - final String locale = pp.locale; + final String locale = pp.locale(); final String title = Locales.getString(locale, "settings.name"); baseGui(title, ROWS_MAIN) - .setItem(SLOT_PARTICLES, toggleItem(locale, "particles", pp.particles), - e -> toggleSetting(pp, () -> pp.particles = !pp.particles)) - .setItem(SLOT_SOUND, toggleItem(locale, "sound", pp.sound), - e -> toggleSetting(pp, () -> pp.sound = !pp.sound)) - .setItem(SLOT_SPECIAL_BLOCKS, toggleItem(locale, "special_blocks", pp.useSpecialBlocks), + .setItem(SLOT_PARTICLES, toggleItem(locale, "particles", pp.settings().particles()), + e -> toggleSetting(pp, () -> pp.settings().setParticles(!pp.settings().particles()))) + .setItem(SLOT_SOUND, toggleItem(locale, "sound", pp.settings().sound()), + e -> toggleSetting(pp, () -> pp.settings().setSound(!pp.settings().sound()))) + .setItem(SLOT_SPECIAL_BLOCKS, toggleItem(locale, "special_blocks", pp.settings().useSpecialBlocks()), e -> toggleSpecialBlocks(pp)) - .setItem(SLOT_FALL_MESSAGE, toggleItem(locale, "fall_message", pp.showFallMessage), - e -> toggleSetting(pp, () -> pp.showFallMessage = !pp.showFallMessage)) - .setItem(SLOT_SCOREBOARD, toggleItem(locale, "scoreboard", pp.showScoreboard), - e -> toggleSetting(pp, () -> pp.showScoreboard = !pp.showScoreboard)) + .setItem(SLOT_FALL_MESSAGE, toggleItem(locale, "fall_message", pp.settings().showFallMessage()), + e -> toggleSetting(pp, () -> pp.settings().setShowFallMessage(!pp.settings().showFallMessage()))) + .setItem(SLOT_SCOREBOARD, toggleItem(locale, "scoreboard", pp.settings().showScoreboard()), + e -> toggleSetting(pp, () -> pp.settings().setShowScoreboard(!pp.settings().showScoreboard()))) .setItem(SLOT_GHOST_RECORDING, toggleItem(locale, "ghost_recording", - Boolean.TRUE.equals(pp.ghostRecording)), e -> toggleGhostRecording(pp)) + Boolean.TRUE.equals(pp.settings().ghostRecording())), e -> toggleGhostRecording(pp)) .setItem(SLOT_STYLE, styleItem(pp), e -> openStyleMenu(pp)) .setItem(SLOT_SCHEM_DIFF, schemDiffItem(pp), e -> cycleSchematicDifficulty(pp)) .setItem(SLOT_CLOSE, closeButton(player), e -> Menus.SETTINGS.open(player)) @@ -76,30 +77,30 @@ public class ParkourSettingsMenu extends LPMenu { private void toggleSetting(ParkourPlayer pp, Runnable update) { update.run(); - pp.updateGeneratorSettings(pp.session.generator); + pp.updateGeneratorSettings(pp.session.generator()); open(pp); } private void toggleSpecialBlocks(ParkourPlayer pp) { - if (pp.session.generator.state.score == 0) { - pp.useSpecialBlocks = !pp.useSpecialBlocks; - pp.updateGeneratorSettings(pp.session.generator); + if (pp.session.generator().state.score() == 0) { + pp.settings().setUseSpecialBlocks(!pp.settings().useSpecialBlocks()); + pp.updateGeneratorSettings(pp.session.generator()); } open(pp); } private void cycleSchematicDifficulty(ParkourPlayer pp) { - if (pp.session.generator.state.score == 0) { + if (pp.session.generator().state.score() == 0) { final List diffs = List.of(0.0, DIFF_QUARTER, DIFF_HALF, DIFF_THREE_QUARTER, 1.0); - final int idx = diffs.indexOf(pp.schematicDifficulty); - pp.schematicDifficulty = diffs.get((idx + 1) % diffs.size()); - pp.updateGeneratorSettings(pp.session.generator); + final int idx = diffs.indexOf(pp.settings().schematicDifficulty()); + pp.settings().setSchematicDifficulty(diffs.get((idx + 1) % diffs.size())); + pp.updateGeneratorSettings(pp.session.generator()); } open(pp); } private void toggleGhostRecording(ParkourPlayer pp) { - pp.ghostRecording = !Boolean.TRUE.equals(pp.ghostRecording); + pp.settings().setGhostRecording(!Boolean.TRUE.equals(pp.settings().ghostRecording())); pp.save(false); open(pp); } @@ -122,16 +123,16 @@ public class ParkourSettingsMenu extends LPMenu { private ItemStack schemDiffItem(ParkourPlayer pp) { final List diffs = List.of(0.0, DIFF_QUARTER, DIFF_HALF, DIFF_THREE_QUARTER, 1.0); - final List values = Locales.getStringList(pp.locale, "settings.parkour_settings.items.schematics.values"); - final int idx = Math.max(0, diffs.indexOf(pp.schematicDifficulty)); - final String label = idx < values.size() ? values.get(idx) : String.valueOf(pp.schematicDifficulty); + final List values = Locales.getStringList(pp.locale(), "settings.parkour_settings.items.schematics.values"); + final int idx = Math.max(0, diffs.indexOf(pp.settings().schematicDifficulty())); + final String label = idx < values.size() ? values.get(idx) : String.valueOf(pp.settings().schematicDifficulty()); final Material[] mats = {Material.RED_STAINED_GLASS_PANE, Material.LIME_STAINED_GLASS_PANE, Material.YELLOW_STAINED_GLASS_PANE, Material.ORANGE_STAINED_GLASS_PANE, Material.SKELETON_SKULL}; final ItemStack item = new ItemStack(idx < mats.length ? mats[idx] : Material.PAPER); final ItemMeta meta = item.getItemMeta(); if (meta != null) { - final String name = Locales.getString(pp.locale, "settings.parkour_settings.items.schematics.name"); - final String lore = Locales.getString(pp.locale, "settings.parkour_settings.items.schematics.lore"); + final String name = Locales.getString(pp.locale(), "settings.parkour_settings.items.schematics.name"); + final String lore = Locales.getString(pp.locale(), "settings.parkour_settings.items.schematics.lore"); meta.setDisplayName(ColorUtil.color(name.replace("%s", label))); applyLore(meta, lore, label); item.setItemMeta(meta); @@ -140,13 +141,13 @@ public class ParkourSettingsMenu extends LPMenu { } private ItemStack styleItem(ParkourPlayer pp) { - final String name = Locales.getString(pp.locale, "settings.parkour_settings.items.styles.item.name"); - final String lore = Locales.getString(pp.locale, "settings.parkour_settings.items.styles.item.lore"); + final String name = Locales.getString(pp.locale(), "settings.parkour_settings.items.styles.item.name"); + final String lore = Locales.getString(pp.locale(), "settings.parkour_settings.items.styles.item.lore"); final ItemStack item = new ItemStack(Material.CRAFTING_TABLE); final ItemMeta meta = item.getItemMeta(); if (meta != null) { - meta.setDisplayName(ColorUtil.color(name.replace("%s", pp.style))); - applyLore(meta, lore, pp.style); + meta.setDisplayName(ColorUtil.color(name.replace("%s", pp.settings().style()))); + applyLore(meta, lore, pp.settings().style()); item.setItemMeta(meta); } return item; @@ -165,7 +166,7 @@ public class ParkourSettingsMenu extends LPMenu { private void openStyleMenu(ParkourPlayer pp) { final Player player = pp.player; - final String locale = pp.locale; + final String locale = pp.locale(); final String title = Locales.getString(locale, "settings.parkour_settings.styles.name"); InventoryGui gui = baseGui(title, ROWS_STYLE); @@ -187,10 +188,9 @@ public class ParkourSettingsMenu extends LPMenu { item.setItemMeta(meta); } - final Style s = style; gui = gui.setItem(slot++, item, e -> { - pp.style = s.getName(); - pp.updateGeneratorSettings(pp.session.generator); + pp.settings().setStyle(style.getName()); + pp.updateGeneratorSettings(pp.session.generator()); open(pp); }); } diff --git a/src/main/java/dev/loki/loparkour/menu/settings/SettingsMenu.java b/src/main/java/dev/loki/loparkour/menu/settings/SettingsMenu.java index 338c610..09b7f6a 100644 --- a/src/main/java/dev/loki/loparkour/menu/settings/SettingsMenu.java +++ b/src/main/java/dev/loki/loparkour/menu/settings/SettingsMenu.java @@ -7,6 +7,7 @@ import dev.loki.loparkour.menu.core.screen.ParkourOption; import dev.loki.loparkour.player.core.ParkourPlayer; import dev.loki.loparkour.player.core.ParkourUser; import dev.loki.loparkour.menu.gui.inventory.InventoryGui; +import dev.loki.loparkour.player.service.UserRegistry; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -24,11 +25,11 @@ public class SettingsMenu extends LPMenu { InventoryGui gui = baseGui(title, ROWS); - if (ParkourOption.PARKOUR_SETTINGS.mayPerform(player) && ParkourPlayer.isPlayer(player)) { + if (ParkourOption.PARKOUR_SETTINGS.mayPerform(player) && UserRegistry.isPlayer(player)) { gui = gui.setItem(SLOT_PARKOUR_SETTINGS, localeItem(player, "settings.parkour_settings.item"), e -> { - final ParkourPlayer pp = ParkourPlayer.getPlayer(player); + final ParkourPlayer pp = UserRegistry.getPlayer(player); if (pp != null) { - pp.session.generator.menu(pp); + pp.session.generator().menu(pp); } }); } @@ -36,7 +37,7 @@ public class SettingsMenu extends LPMenu { if (ParkourOption.LANG.mayPerform(player) && ParkourUser.isUser(player)) { final String langName = Locales.getString(locale, "name"); gui = gui.setItem(SLOT_LANG, localeItem(locale, "settings.lang.item", langName), e -> { - final ParkourPlayer pp = ParkourPlayer.getPlayer(player); + final ParkourPlayer pp = UserRegistry.getPlayer(player); if (pp != null) { Menus.LANG.open(pp); } diff --git a/src/main/java/dev/loki/loparkour/mode/base/Modes.java b/src/main/java/dev/loki/loparkour/mode/base/Modes.java index 90b5fdd..c0bccfe 100644 --- a/src/main/java/dev/loki/loparkour/mode/base/Modes.java +++ b/src/main/java/dev/loki/loparkour/mode/base/Modes.java @@ -21,35 +21,91 @@ public final class Modes { private Modes() { } - public static DefaultMode DEFAULT; - public static SpectatorMode SPECTATOR; - public static SpeedrunMode SPEEDRUN; - public static InvisibleBarrierMode INVISIBLE_BARRIER; - public static RaceMode RACE; - public static CoopMode COOP; - public static GravityShiftMode GRAVITY_SHIFT; - public static ElytraMode ELYTRA; - public static SpeedDemonMode SPEED_DEMON; - public static MinSpeedMode MIN_SPEED; - public static TimeTrialMode TIME_TRIAL; - public static CloseMode CLOSE; - public static ObstacleMode OBSTACLE; - public static DuelsMode DUELS; + private static DefaultMode defaultMode; + private static SpectatorMode spectator; + private static SpeedrunMode speedrun; + private static InvisibleBarrierMode invisibleBarrier; + private static RaceMode race; + private static CoopMode coop; + private static GravityShiftMode gravityShift; + private static ElytraMode elytra; + private static SpeedDemonMode speedDemon; + private static MinSpeedMode minSpeed; + private static TimeTrialMode timeTrial; + private static CloseMode close; + private static ObstacleMode obstacle; + private static DuelsMode duels; public static void init() { - DEFAULT = (DefaultMode) Registry.getMode("default"); - SPECTATOR = (SpectatorMode) Registry.getMode("spectator"); - SPEEDRUN = (SpeedrunMode) Registry.getMode("speedrun"); - INVISIBLE_BARRIER = (InvisibleBarrierMode) Registry.getMode("invisible-barrier"); - RACE = (RaceMode) Registry.getMode("race"); - COOP = (CoopMode) Registry.getMode("coop"); - GRAVITY_SHIFT = (GravityShiftMode) Registry.getMode("gravity-shift"); - ELYTRA = (ElytraMode) Registry.getMode("elytra"); - SPEED_DEMON = (SpeedDemonMode) Registry.getMode("speeddemon"); - MIN_SPEED = (MinSpeedMode) Registry.getMode("minspeed"); - TIME_TRIAL = (TimeTrialMode) Registry.getMode("timetrial"); - CLOSE = (CloseMode) Registry.getMode("close"); - OBSTACLE = (ObstacleMode) Registry.getMode("obstacle"); - DUELS = (DuelsMode) Registry.getMode("duels"); + defaultMode = (DefaultMode) Registry.getMode("default"); + spectator = (SpectatorMode) Registry.getMode("spectator"); + speedrun = (SpeedrunMode) Registry.getMode("speedrun"); + invisibleBarrier = (InvisibleBarrierMode) Registry.getMode("invisible-barrier"); + race = (RaceMode) Registry.getMode("race"); + coop = (CoopMode) Registry.getMode("coop"); + gravityShift = (GravityShiftMode) Registry.getMode("gravity-shift"); + elytra = (ElytraMode) Registry.getMode("elytra"); + speedDemon = (SpeedDemonMode) Registry.getMode("speeddemon"); + minSpeed = (MinSpeedMode) Registry.getMode("minspeed"); + timeTrial = (TimeTrialMode) Registry.getMode("timetrial"); + close = (CloseMode) Registry.getMode("close"); + obstacle = (ObstacleMode) Registry.getMode("obstacle"); + duels = (DuelsMode) Registry.getMode("duels"); + } + + public static DefaultMode defaultMode() { + return defaultMode; + } + + public static SpectatorMode spectator() { + return spectator; + } + + public static SpeedrunMode speedrun() { + return speedrun; + } + + public static InvisibleBarrierMode invisibleBarrier() { + return invisibleBarrier; + } + + public static RaceMode race() { + return race; + } + + public static CoopMode coop() { + return coop; + } + + public static GravityShiftMode gravityShift() { + return gravityShift; + } + + public static ElytraMode elytra() { + return elytra; + } + + public static SpeedDemonMode speedDemon() { + return speedDemon; + } + + public static MinSpeedMode minSpeed() { + return minSpeed; + } + + public static TimeTrialMode timeTrial() { + return timeTrial; + } + + public static CloseMode close() { + return close; + } + + public static ObstacleMode obstacle() { + return obstacle; + } + + public static DuelsMode duels() { + return duels; } } diff --git a/src/main/java/dev/loki/loparkour/mode/impl/barrier/BarrierGenerator.java b/src/main/java/dev/loki/loparkour/mode/impl/barrier/BarrierGenerator.java index fdb363c..35059eb 100644 --- a/src/main/java/dev/loki/loparkour/mode/impl/barrier/BarrierGenerator.java +++ b/src/main/java/dev/loki/loparkour/mode/impl/barrier/BarrierGenerator.java @@ -44,6 +44,6 @@ class BarrierGenerator extends ParkourGenerator { @Override public Mode getMode() { - return Modes.INVISIBLE_BARRIER; + return Modes.invisibleBarrier(); } } diff --git a/src/main/java/dev/loki/loparkour/mode/impl/barrier/InvisibleBarrierMode.java b/src/main/java/dev/loki/loparkour/mode/impl/barrier/InvisibleBarrierMode.java index 96b0b8e..68b11c0 100644 --- a/src/main/java/dev/loki/loparkour/mode/impl/barrier/InvisibleBarrierMode.java +++ b/src/main/java/dev/loki/loparkour/mode/impl/barrier/InvisibleBarrierMode.java @@ -7,6 +7,7 @@ import dev.loki.loparkour.mode.base.ModeMessages; import dev.loki.loparkour.player.core.ParkourPlayer; import dev.loki.loparkour.session.core.Session; import dev.loki.loparkour.util.item.Item; +import dev.loki.loparkour.player.service.UserRegistry; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -39,8 +40,8 @@ public class InvisibleBarrierMode implements Mode { return; } - final ParkourPlayer pp = ParkourPlayer.getPlayer(player); - if (pp != null && pp.session.generator instanceof BarrierGenerator) { + final ParkourPlayer pp = UserRegistry.getPlayer(player); + if (pp != null && pp.session.generator() instanceof BarrierGenerator) { return; } diff --git a/src/main/java/dev/loki/loparkour/mode/impl/multi/CoopGenerator.java b/src/main/java/dev/loki/loparkour/mode/impl/multi/CoopGenerator.java index 99bc091..fe85f31 100644 --- a/src/main/java/dev/loki/loparkour/mode/impl/multi/CoopGenerator.java +++ b/src/main/java/dev/loki/loparkour/mode/impl/multi/CoopGenerator.java @@ -42,11 +42,11 @@ class CoopGenerator extends ParkourGenerator { contributions.compute(pp.getUUID(), (uuid, count) -> (count == null ? 0 : count) + 1); } - if (state.score % MILESTONE_INTERVAL == 0) { + if (state.score() % MILESTONE_INTERVAL == 0) { getPlayers().forEach(pp -> { pp.player.sendTitle( - ColorUtil.color(Locales.getString(pp.locale, "modes.coop.milestone_title").formatted(state.score)), - ColorUtil.color(Locales.getString(pp.locale, "modes.coop.milestone_subtitle")), + ColorUtil.color(Locales.getString(pp.locale(), "modes.coop.milestone_title").formatted(state.score())), + ColorUtil.color(Locales.getString(pp.locale(), "modes.coop.milestone_subtitle")), TITLE_FADE_IN, TITLE_STAY, TITLE_FADE_OUT); pp.player.playSound(pp.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, MILESTONE_VOLUME, MILESTONE_PITCH); @@ -65,9 +65,9 @@ class CoopGenerator extends ParkourGenerator { public void fall() { getPlayers().forEach(pp -> { final int contrib = contributions.getOrDefault(pp.getUUID(), 0); - final int pct = state.score > 0 ? (contrib * PERCENT_MULTIPLIER / state.score) : 0; + final int pct = state.score() > 0 ? (contrib * PERCENT_MULTIPLIER / state.score()) : 0; pp.sendTranslated("modes.coop.stats", - Integer.toString(state.score), + Integer.toString(state.score()), Integer.toString(contrib), Integer.toString(pct)); }); @@ -84,6 +84,6 @@ class CoopGenerator extends ParkourGenerator { @Override public Mode getMode() { - return Modes.COOP; + return Modes.coop(); } } diff --git a/src/main/java/dev/loki/loparkour/mode/impl/multi/CoopMode.java b/src/main/java/dev/loki/loparkour/mode/impl/multi/CoopMode.java index e72f9b9..b319ec3 100644 --- a/src/main/java/dev/loki/loparkour/mode/impl/multi/CoopMode.java +++ b/src/main/java/dev/loki/loparkour/mode/impl/multi/CoopMode.java @@ -8,6 +8,7 @@ import dev.loki.loparkour.mode.base.MultiMode; import dev.loki.loparkour.player.core.ParkourPlayer; import dev.loki.loparkour.player.core.ParkourUser; import dev.loki.loparkour.session.core.Session; +import dev.loki.loparkour.player.service.UserRegistry; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -51,8 +52,8 @@ public class CoopMode implements MultiMode { return; } - final ParkourPlayer pp = ParkourPlayer.getPlayer(player); - if (pp != null && pp.session.generator instanceof CoopGenerator) { + final ParkourPlayer pp = UserRegistry.getPlayer(player); + if (pp != null && pp.session.generator() instanceof CoopGenerator) { return; } @@ -70,14 +71,14 @@ public class CoopMode implements MultiMode { return; } - if (session.generator == null) { + if (session.generator() == null) { return; } final ParkourPlayer pp = ParkourUser.register(joiningPlayer, session); session.addPlayers(pp); - pp.updateGeneratorSettings(session.generator); - if (session.generator instanceof CoopGenerator coopGenerator) { + pp.updateGeneratorSettings(session.generator()); + if (session.generator() instanceof CoopGenerator coopGenerator) { coopGenerator.onPlayerJoin(pp); } @@ -87,7 +88,7 @@ public class CoopMode implements MultiMode { @Override public void leave(Player player, Session session) { - final ParkourPlayer pp = ParkourPlayer.getPlayer(player); + final ParkourPlayer pp = UserRegistry.getPlayer(player); if (pp == null) { return; } diff --git a/src/main/java/dev/loki/loparkour/mode/impl/multi/RaceMode.java b/src/main/java/dev/loki/loparkour/mode/impl/multi/RaceMode.java index 7e25e02..7c3f332 100644 --- a/src/main/java/dev/loki/loparkour/mode/impl/multi/RaceMode.java +++ b/src/main/java/dev/loki/loparkour/mode/impl/multi/RaceMode.java @@ -12,6 +12,7 @@ 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.player.service.UserRegistry; import org.bukkit.Sound; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -25,7 +26,7 @@ import org.jetbrains.annotations.Nullable; *
    *
  • {@code tick()} called {@code super.tick()} AND checked score every tick — * but super.tick() already calls {@code score()} internally, so the finish - * check ran one tick late causing an off-by-one where state.score could + * check ran one tick late causing an off-by-one where state.score() could * briefly exceed targetScore before finishing. Fixed by overriding * {@link #score()} instead — finish check fires the exact moment target is hit.
  • *
  • ActionBar was re-sent every tick inside tick() which conflicted with the @@ -64,8 +65,8 @@ public class RaceMode implements Mode { return; } - final ParkourPlayer pp = ParkourPlayer.getPlayer(player); - if (pp != null && pp.session.generator instanceof RaceGenerator) { + final ParkourPlayer pp = UserRegistry.getPlayer(player); + if (pp != null && pp.session.generator() instanceof RaceGenerator) { return; } @@ -102,25 +103,25 @@ public class RaceMode implements Mode { // Update action bar on every new block updateProgressBar(); - if (!raceFinished && state.score >= targetScore) { + if (!raceFinished && state.score() >= targetScore) { finishRace(); } } @SuppressWarnings("deprecation") // Spigot Chat API: action bar via TextComponent private void updateProgressBar() { - final int remaining = Math.max(0, targetScore - state.score); - final int filledCount = Math.min(BAR_SEGMENTS, state.score * BAR_SEGMENTS / Math.max(targetScore, 1)); + final int remaining = Math.max(0, targetScore - state.score()); + final int filledCount = Math.min(BAR_SEGMENTS, state.score() * BAR_SEGMENTS / Math.max(targetScore, 1)); final String filled = ColorUtil.color("7FF55") + "█".repeat(filledCount); final String empty = ColorUtil.color("&#A0A0A0") + "█".repeat(BAR_SEGMENTS - filledCount); getPlayers().forEach(pp -> { final String suffix = remaining > 0 - ? Locales.getString(pp.locale, "modes.race.action_bar_left").formatted(remaining) - : Locales.getString(pp.locale, "modes.race.action_bar_done"); + ? Locales.getString(pp.locale(), "modes.race.action_bar_left").formatted(remaining) + : Locales.getString(pp.locale(), "modes.race.action_bar_done"); final String message = ColorUtil.color( - Locales.getString(pp.locale, "modes.race.action_bar") - .formatted(filled + empty, state.score, targetScore, suffix)); + Locales.getString(pp.locale(), "modes.race.action_bar") + .formatted(filled + empty, state.score(), targetScore, suffix)); pp.player.spigot().sendMessage( net.md_5.bungee.api.ChatMessageType.ACTION_BAR, net.md_5.bungee.api.chat.TextComponent.fromLegacyText(message)); @@ -133,8 +134,8 @@ public class RaceMode implements Mode { getPlayers().forEach(pp -> { pp.player.sendTitle( - ColorUtil.color(Locales.getString(pp.locale, "modes.race.title")), - ColorUtil.color(Locales.getString(pp.locale, "modes.race.subtitle").formatted(time)), + ColorUtil.color(Locales.getString(pp.locale(), "modes.race.title")), + ColorUtil.color(Locales.getString(pp.locale(), "modes.race.subtitle").formatted(time)), TITLE_FADE_IN, TITLE_STAY, TITLE_FADE_OUT); pp.player.playSound(pp.getLocation(), Sound.UI_TOAST_CHALLENGE_COMPLETE, 1f, 1f); @@ -148,7 +149,7 @@ public class RaceMode implements Mode { // Return to lobby after 5 seconds — guarded against null/closed session dev.loki.loparkour.scheduler.Scheduler.get(LoParkour.getPlugin()) .runLater(() -> { - if (session != null && !state.stopped) { + if (session != null && !state.stopped()) { reset(false); } }, LOBBY_RESET_DELAY_TICKS); @@ -162,7 +163,7 @@ public class RaceMode implements Mode { @Override public Mode getMode() { - return Modes.RACE; + return Modes.race(); } } } diff --git a/src/main/java/dev/loki/loparkour/mode/impl/multi/SpectatorMode.java b/src/main/java/dev/loki/loparkour/mode/impl/multi/SpectatorMode.java index a9bd05e..bb6d892 100644 --- a/src/main/java/dev/loki/loparkour/mode/impl/multi/SpectatorMode.java +++ b/src/main/java/dev/loki/loparkour/mode/impl/multi/SpectatorMode.java @@ -57,7 +57,7 @@ public class SpectatorMode implements Mode { if (user != null) { ParkourUser.unregister(user, false, false, false); - spectator = new ParkourSpectator(player, session, user.previousData); + spectator = new ParkourSpectator(player, session, user.previousData()); } else { spectator = new ParkourSpectator(player, session, null); } diff --git a/src/main/java/dev/loki/loparkour/mode/impl/solo/DefaultMode.java b/src/main/java/dev/loki/loparkour/mode/impl/solo/DefaultMode.java index f09b63c..71eeec4 100644 --- a/src/main/java/dev/loki/loparkour/mode/impl/solo/DefaultMode.java +++ b/src/main/java/dev/loki/loparkour/mode/impl/solo/DefaultMode.java @@ -7,6 +7,7 @@ import dev.loki.loparkour.mode.base.ModeMessages; 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.player.service.UserRegistry; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -43,8 +44,8 @@ public class DefaultMode implements Mode { return; } - final ParkourPlayer pp = ParkourPlayer.getPlayer(player); - if (pp != null && pp.session.generator != null && pp.session.generator.getMode() instanceof DefaultMode) { + final ParkourPlayer pp = UserRegistry.getPlayer(player); + if (pp != null && pp.session.generator() != null && pp.session.generator().getMode() instanceof DefaultMode) { return; } diff --git a/src/main/java/dev/loki/loparkour/mode/impl/solo/GravityShiftMode.java b/src/main/java/dev/loki/loparkour/mode/impl/solo/GravityShiftMode.java index 6b58e1e..27f2044 100644 --- a/src/main/java/dev/loki/loparkour/mode/impl/solo/GravityShiftMode.java +++ b/src/main/java/dev/loki/loparkour/mode/impl/solo/GravityShiftMode.java @@ -12,6 +12,7 @@ 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 dev.loki.loparkour.player.service.UserRegistry; import org.bukkit.entity.Player; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; @@ -56,8 +57,8 @@ public class GravityShiftMode implements Mode { return; } - final ParkourPlayer pp = ParkourPlayer.getPlayer(player); - if (pp != null && pp.session.generator instanceof GravityShiftGenerator) { + final ParkourPlayer pp = UserRegistry.getPlayer(player); + if (pp != null && pp.session.generator() instanceof GravityShiftGenerator) { return; } @@ -170,7 +171,7 @@ public class GravityShiftMode implements Mode { @Override public Mode getMode() { - return Modes.GRAVITY_SHIFT; + return Modes.gravityShift(); } private record EffectConfig(int amplifier, int durationTicks) { } diff --git a/src/main/java/dev/loki/loparkour/mode/impl/solo/SpeedrunMode.java b/src/main/java/dev/loki/loparkour/mode/impl/solo/SpeedrunMode.java index 062d714..f0365be 100644 --- a/src/main/java/dev/loki/loparkour/mode/impl/solo/SpeedrunMode.java +++ b/src/main/java/dev/loki/loparkour/mode/impl/solo/SpeedrunMode.java @@ -14,6 +14,7 @@ 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.player.service.UserRegistry; import org.bukkit.Material; import org.bukkit.Particle; import org.bukkit.Sound; @@ -67,8 +68,8 @@ public class SpeedrunMode implements Mode { return; } - final ParkourPlayer pp = ParkourPlayer.getPlayer(player); - if (pp != null && pp.session.generator instanceof SpeedrunGenerator) { + final ParkourPlayer pp = UserRegistry.getPlayer(player); + if (pp != null && pp.session.generator() instanceof SpeedrunGenerator) { return; } @@ -170,7 +171,7 @@ public class SpeedrunMode implements Mode { @Override public Mode getMode() { - return Modes.SPEEDRUN; + return Modes.speedrun(); } } } diff --git a/src/main/java/dev/loki/loparkour/player/core/ParkourPlayer.java b/src/main/java/dev/loki/loparkour/player/core/ParkourPlayer.java index 7047e53..f93a620 100644 --- a/src/main/java/dev/loki/loparkour/player/core/ParkourPlayer.java +++ b/src/main/java/dev/loki/loparkour/player/core/ParkourPlayer.java @@ -1,111 +1,50 @@ package dev.loki.loparkour.player.core; -import com.google.gson.annotations.Expose; import dev.loki.loparkour.LoParkour; import dev.loki.loparkour.generator.core.coordinator.ParkourGenerator; -import dev.loki.loparkour.generator.core.model.Profile; -import dev.loki.loparkour.menu.core.screen.ParkourOption; import dev.loki.loparkour.mode.base.MultiMode; +import dev.loki.loparkour.player.data.PlayerSettings; import dev.loki.loparkour.player.data.PreviousData; import dev.loki.loparkour.player.service.PlayerSettingsManager; import dev.loki.loparkour.player.service.PlayerSpawnSetup; import dev.loki.loparkour.session.core.Session; -import dev.loki.loparkour.world.core.Divider; import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.entity.Player; -import org.bukkit.potion.PotionEffect; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.List; -import java.util.Map; -import java.util.function.BiConsumer; - /** - * Subclass of {@link ParkourUser}. This class is used for players who are actively playing Parkour in any (default) mode. + * Subclass of {@link ParkourUser} for players actively playing Parkour in any (default) mode. * * @author loki */ public class ParkourPlayer extends ParkourUser { - public static final Map PLAYER_COLUMNS = PlayerSettingsManager.getColumnMappings(); - - public @Expose Double schematicDifficulty; - public @Expose Integer blockLead; - public @Expose Boolean particles; - public @Expose Boolean sound; - public @Expose Boolean useSpecialBlocks; - public @Expose Boolean showFallMessage; - public @Expose Boolean showScoreboard; - public @Expose Boolean ghostRecording; - public @Expose Integer selectedTime; - public @Expose String style; - public @Expose String locale; - public @Expose List collectedRewards; - + private final PlayerSettings settings = new PlayerSettings(); private final PlayerScoringTracker scoring = new PlayerScoringTracker(); private final PlayerPersistence persistence = new PlayerPersistence(); private final PlayerSpawnSetup spawnSetup = new PlayerSpawnSetup(); - /** - * Creates a new instance of a ParkourPlayer
    - * If you are using the API, please use {@link ParkourPlayer#register(Player, Session)} instead - */ + /** Creates a ParkourPlayer; API users should call {@link ParkourUser#register(Player, Session)} instead. */ public ParkourPlayer(@NotNull Player player, @NotNull Session session, @Nullable PreviousData previousData) { super(player, session, previousData); - // generic player settings player.setFlying(false); player.setAllowFlight(false); player.setInvisible(false); - - for (final PotionEffect effect : player.getActivePotionEffects()) { - player.removePotionEffect(effect.getType()); - } - } - - /** - * @param player The player. - * @return True when this player is a {@link ParkourPlayer}, false if not. - */ - public static boolean isPlayer(@Nullable Player player) { - return player != null && getPlayers().stream().anyMatch(other -> other.player == player); - } - - /** - * @param player The player. - * @return player as a {@link ParkourPlayer}, null if not found. - */ - public static @Nullable ParkourPlayer getPlayer(@NotNull Player player) { - // Optimized: search directly in sessions without creating intermediate list - return Divider.SECTIONS.keySet().stream() - .flatMap(session -> session.getPlayers().stream()) - .filter(other -> other.getUUID().equals(player.getUniqueId())) - .findAny() - .orElse(null); - } - - /** - * @return List with all players. - */ - public static List getPlayers() { - return Divider.SECTIONS.keySet().stream() - .flatMap(session -> session.getPlayers().stream()) - .toList(); + player.getActivePotionEffects().forEach(effect -> player.removePotionEffect(effect.getType())); } @Override public void unregister() { - if (session.generator != null && - session.generator.getMode() != null && - session.generator.getMode() instanceof MultiMode mode) { + if (session.generator() != null && + session.generator().getMode() != null && + session.generator().getMode() instanceof MultiMode mode) { mode.leave(player, session); } session.removePlayers(this); - - // Clear scored blocks to prevent memory leak scoring.clear(); if (LoParkour.getGhostService() != null) { @@ -115,59 +54,32 @@ public class ParkourPlayer extends ParkourUser { persistence.save(this, LoParkour.getPlugin().isEnabled()); } - /** - * Clear scored blocks set, typically on fall/respawn so same coordinates can be re-scored. - */ + /** Clear scored blocks set, typically on fall/respawn so same coordinates can be re-scored. */ public void clearScoredBlocks() { scoring.clear(); } - /** - * Check if player has already scored on this block. - */ + /** Whether this player has already scored on this block. */ public boolean hasScored(Block block) { return scoring.hasScored(block); } - /** - * Mark block as scored to prevent duplicate scoring. - */ + /** Mark block as scored to prevent duplicate scoring. */ public void markScored(Block block) { scoring.markScored(block); } - /** - * Sets the user's settings. If an item is not included, the setting gets reset. - * - * @param settings The settings map. - */ - public void setSettings(@NotNull Map settings) { - PlayerSettingsManager.applySettings(this, settings); + /** Preferences of this player: what to show, which style, how hard the schematics may be. */ + public PlayerSettings settings() { + return settings; } - /** - * Forces this player's generator to match the settings of this player. - */ + /** Forces this player's generator to match the settings of this player. */ public void updateGeneratorSettings(ParkourGenerator generator) { - final Profile profile = generator.profile; - - profile.set("schematicDifficulty", schematicDifficulty.toString()) - .set("blockLead", blockLead.toString()) - .set("particles", particles.toString()) - .set("sound", sound.toString()) - .set("useSpecialBlocks", useSpecialBlocks.toString()) - .set("showFallMessage", showFallMessage.toString()) - .set("showScoreboard", showScoreboard.toString()) - .set("selectedTime", selectedTime.toString()) - .set("style", style); - - generator.profileManager.overrideProfile(); - generator.profileManager.calculateChances(); + PlayerSettingsManager.applyToGenerator(this, generator); } - /** - * Saves the player's data to their file - */ + /** Saves the player's data to their file. */ public void save(boolean async) { persistence.save(this, async); } @@ -176,13 +88,8 @@ public class ParkourPlayer extends ParkourUser { spawnSetup.setup(this, to); } - /** - * Updates hotbar items with current language - */ + /** Updates hotbar items with the current language. */ public void updateHotbar() { spawnSetup.updateHotbar(this); } - - public record OptionContainer(ParkourOption option, BiConsumer consumer) { - } } diff --git a/src/main/java/dev/loki/loparkour/player/core/ParkourUser.java b/src/main/java/dev/loki/loparkour/player/core/ParkourUser.java index 5231b08..12b5a5f 100644 --- a/src/main/java/dev/loki/loparkour/player/core/ParkourUser.java +++ b/src/main/java/dev/loki/loparkour/player/core/ParkourUser.java @@ -62,16 +62,16 @@ public abstract class ParkourUser { // ─── Fields ─────────────────────────────────────────────────────────────── - @NotNull public String locale = OptionGeneral.optionsDefaults().get(ParkourOption.LANG); - @Nullable public Scoreboard board; - @NotNull public PreviousData previousData; - public Session.ChatType chatType = Session.ChatType.PUBLIC; + @NotNull private String locale = OptionGeneral.optionsDefaults().get(ParkourOption.LANG); + @Nullable private Scoreboard board; + @NotNull private PreviousData previousData; + private Session.ChatType chatType = Session.ChatType.PUBLIC; public final Session session; public final Player player; public final Instant joined; private ScoreboardManager scoreboardManager; /** True while unregister is in progress; re-entrant unregister calls become no-ops. */ - public boolean unregistering; + private boolean unregistering; @SuppressWarnings("deprecation") // Scoreboard: legacy registerNewObjective(String,String,String) public ParkourUser(@NotNull Player player, @NotNull Session session, @Nullable PreviousData previousData) { @@ -93,6 +93,38 @@ public abstract class ParkourUser { public abstract void unregister(); + public @NotNull String locale() { + return locale; + } + + public void setLocale(@NotNull String locale) { + this.locale = locale; + } + + public @Nullable Scoreboard board() { + return board; + } + + public void setBoard(@Nullable Scoreboard board) { + this.board = board; + } + + public @NotNull PreviousData previousData() { + return previousData; + } + + public Session.ChatType chatType() { + return chatType; + } + + public boolean unregistering() { + return unregistering; + } + + public void setUnregistering(boolean unregistering) { + this.unregistering = unregistering; + } + public void teleport(@NotNull Location to) { player.teleport(to); } diff --git a/src/main/java/dev/loki/loparkour/player/data/PlayerSettings.java b/src/main/java/dev/loki/loparkour/player/data/PlayerSettings.java new file mode 100644 index 0000000..d2fa9be --- /dev/null +++ b/src/main/java/dev/loki/loparkour/player/data/PlayerSettings.java @@ -0,0 +1,115 @@ +package dev.loki.loparkour.player.data; + +import java.util.ArrayList; +import java.util.List; + +/** + * Per-player parkour preferences: what to show, which style to play, how hard the schematics may be. + * + *

    Persistence goes through the flat key/value map built by PlayerSettingsManager, so the storage keys + * are stable and independent of these field names. + * + * @author loki + */ +public class PlayerSettings { + + private Double schematicDifficulty; + private Integer blockLead; + private Boolean particles; + private Boolean sound; + private Boolean useSpecialBlocks; + private Boolean showFallMessage; + private Boolean showScoreboard; + private Boolean ghostRecording; + private Integer selectedTime; + private String style; + private List collectedRewards; + + public Double schematicDifficulty() { + return schematicDifficulty; + } + + public void setSchematicDifficulty(Double schematicDifficulty) { + this.schematicDifficulty = schematicDifficulty; + } + + public Integer blockLead() { + return blockLead; + } + + public void setBlockLead(Integer blockLead) { + this.blockLead = blockLead; + } + + public Boolean particles() { + return particles; + } + + public void setParticles(Boolean particles) { + this.particles = particles; + } + + public Boolean sound() { + return sound; + } + + public void setSound(Boolean sound) { + this.sound = sound; + } + + public Boolean useSpecialBlocks() { + return useSpecialBlocks; + } + + public void setUseSpecialBlocks(Boolean useSpecialBlocks) { + this.useSpecialBlocks = useSpecialBlocks; + } + + public Boolean showFallMessage() { + return showFallMessage; + } + + public void setShowFallMessage(Boolean showFallMessage) { + this.showFallMessage = showFallMessage; + } + + public Boolean showScoreboard() { + return showScoreboard; + } + + public void setShowScoreboard(Boolean showScoreboard) { + this.showScoreboard = showScoreboard; + } + + public Boolean ghostRecording() { + return ghostRecording; + } + + public void setGhostRecording(Boolean ghostRecording) { + this.ghostRecording = ghostRecording; + } + + public Integer selectedTime() { + return selectedTime; + } + + public void setSelectedTime(Integer selectedTime) { + this.selectedTime = selectedTime; + } + + public String style() { + return style; + } + + public void setStyle(String style) { + this.style = style; + } + + /** Reward keys already claimed by this player; never null after first access. */ + public List collectedRewards() { + if (collectedRewards == null) { + collectedRewards = new ArrayList<>(); + } + return collectedRewards; + } +} diff --git a/src/main/java/dev/loki/loparkour/player/data/PreviousData.java b/src/main/java/dev/loki/loparkour/player/data/PreviousData.java index 657b2e1..c4c267f 100644 --- a/src/main/java/dev/loki/loparkour/player/data/PreviousData.java +++ b/src/main/java/dev/loki/loparkour/player/data/PreviousData.java @@ -33,7 +33,7 @@ public class PreviousData { /** * List of all {@link Reward} to execute on leave. */ - public List onLeave = new ArrayList<>(); + public final List onLeave = new ArrayList<>(); public PreviousData(@NotNull Player player) { gamemode = player.getGameMode(); diff --git a/src/main/java/dev/loki/loparkour/player/service/PlayerSettingsManager.java b/src/main/java/dev/loki/loparkour/player/service/PlayerSettingsManager.java index 149546f..cb46833 100644 --- a/src/main/java/dev/loki/loparkour/player/service/PlayerSettingsManager.java +++ b/src/main/java/dev/loki/loparkour/player/service/PlayerSettingsManager.java @@ -1,13 +1,16 @@ package dev.loki.loparkour.player.service; import dev.loki.loparkour.config.options.OptionGeneral; +import dev.loki.loparkour.generator.core.coordinator.ParkourGenerator; +import dev.loki.loparkour.generator.core.model.Profile; import dev.loki.loparkour.player.core.ParkourPlayer; +import dev.loki.loparkour.player.data.PlayerSettings; import dev.loki.loparkour.menu.core.screen.ParkourOption; -import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.function.BiConsumer; @@ -30,44 +33,98 @@ public final class PlayerSettingsManager { } private static void initializeColumnMappings() { - COLUMN_MAPPINGS.put("uuid", new OptionContainer(null, null)); - COLUMN_MAPPINGS.put("style", new OptionContainer(ParkourOption.STYLES, (p, v) -> p.style = v)); - COLUMN_MAPPINGS.put("blockLead", new OptionContainer(ParkourOption.LEADS, (p, v) -> - p.blockLead = parseIntSafe(v, defaultBlockLead()))); - COLUMN_MAPPINGS.put("useParticles", new OptionContainer(ParkourOption.PARTICLES, (p, v) -> p.particles = parseBoolean(v))); - COLUMN_MAPPINGS.put("useSpecial", new OptionContainer(ParkourOption.SPECIAL_BLOCKS, (p, v) -> p.useSpecialBlocks = parseBoolean(v))); - COLUMN_MAPPINGS.put("showFallMsg", new OptionContainer(ParkourOption.FALL_MESSAGE, (p, v) -> p.showFallMessage = parseBoolean(v))); - COLUMN_MAPPINGS.put("showScoreboard", new OptionContainer(ParkourOption.SCOREBOARD, (p, v) -> p.showScoreboard = parseBoolean(v))); - COLUMN_MAPPINGS.put("selectedTime", new OptionContainer(ParkourOption.TIME, (p, v) -> - p.selectedTime = parseIntSafe(v, defaultSelectedTime()))); - COLUMN_MAPPINGS.put("collectedRewards", new OptionContainer(null, PlayerSettingsManager::applyCollectedRewards)); - COLUMN_MAPPINGS.put("locale", new OptionContainer(ParkourOption.LANG, (p, v) -> { - // Sanitize legacy Boolean values from old config parsing - String sanitized = v; - if ("true".equals(v) || "false".equals(v)) { - sanitized = OptionGeneral.optionsDefaults().getOrDefault(ParkourOption.LANG, "en"); - } - p.locale = sanitized; - })); - COLUMN_MAPPINGS.put("schematicDifficulty", new OptionContainer(ParkourOption.SCHEMATICS, (p, v) -> - p.schematicDifficulty = parseDoubleSafe(v, defaultSchematicDifficulty()))); - COLUMN_MAPPINGS.put("sound", new OptionContainer(ParkourOption.SOUND, (p, v) -> p.sound = parseBoolean(v))); - COLUMN_MAPPINGS.put("ghostRecording", new OptionContainer(ParkourOption.GHOST_RECORDING, (p, v) -> p.ghostRecording = parseBoolean(v))); + put("uuid", null, null); + put("style", ParkourOption.STYLES, (p, v) -> p.settings().setStyle(v)); + put("blockLead", ParkourOption.LEADS, (p, v) -> p.settings().setBlockLead(parseIntSafe(v, defaultBlockLead()))); + put("useParticles", ParkourOption.PARTICLES, (p, v) -> p.settings().setParticles(parseBoolean(v))); + put("useSpecial", ParkourOption.SPECIAL_BLOCKS, (p, v) -> p.settings().setUseSpecialBlocks(parseBoolean(v))); + put("showFallMsg", ParkourOption.FALL_MESSAGE, (p, v) -> p.settings().setShowFallMessage(parseBoolean(v))); + put("showScoreboard", ParkourOption.SCOREBOARD, (p, v) -> p.settings().setShowScoreboard(parseBoolean(v))); + put("selectedTime", ParkourOption.TIME, (p, v) -> p.settings().setSelectedTime(parseIntSafe(v, defaultSelectedTime()))); + put("collectedRewards", null, PlayerSettingsManager::applyCollectedRewards); + put("locale", ParkourOption.LANG, (p, v) -> p.setLocale(sanitizeLocale(v))); + put("schematicDifficulty", ParkourOption.SCHEMATICS, (p, v) -> + p.settings().setSchematicDifficulty(parseDoubleSafe(v, defaultSchematicDifficulty()))); + put("sound", ParkourOption.SOUND, (p, v) -> p.settings().setSound(parseBoolean(v))); + put("ghostRecording", ParkourOption.GHOST_RECORDING, (p, v) -> p.settings().setGhostRecording(parseBoolean(v))); } - public static Map getColumnMappings() { - return COLUMN_MAPPINGS; + private static void put(String key, ParkourOption option, BiConsumer consumer) { + COLUMN_MAPPINGS.put(key, new OptionContainer(option, consumer)); + } + + /** + * Language code safe to use: legacy data stored booleans and numbers where a code belonged. + */ + public static String sanitizeLocale(String value) { + if (value == null || value.isEmpty() || "true".equals(value) || "false".equals(value) + || "1".equals(value) || "0".equals(value)) { + return OptionGeneral.optionsDefaults().getOrDefault(ParkourOption.LANG, "en"); + } + return value; + } + + /** + * The persisted settings of a player, keyed identically for the disk and the SQL backends. + */ + public static Map toSettingsMap(ParkourPlayer player) { + final PlayerSettings settings = player.settings(); + final Map persisted = new HashMap<>(); + + persisted.put("style", settings.style()); + persisted.put("blockLead", settings.blockLead()); + persisted.put("useParticles", settings.particles()); + persisted.put("useSpecial", settings.useSpecialBlocks()); + persisted.put("showFallMsg", settings.showFallMessage()); + persisted.put("showScoreboard", settings.showScoreboard()); + persisted.put("selectedTime", settings.selectedTime()); + persisted.put("ghostRecording", settings.ghostRecording()); + persisted.put("collectedRewards", settings.collectedRewards()); + persisted.put("locale", sanitizeLocale(player.locale())); + persisted.put("schematicDifficulty", settings.schematicDifficulty()); + persisted.put("sound", settings.sound()); + + return persisted; + } + + /** + * Push a player's settings into their generator profile and recompute the jump chances. + */ + public static void applyToGenerator(ParkourPlayer player, ParkourGenerator generator) { + final Profile profile = generator.profile; + final PlayerSettings settings = player.settings(); + + profile.set("schematicDifficulty", settings.schematicDifficulty().toString()) + .set("blockLead", settings.blockLead().toString()) + .set("particles", settings.particles().toString()) + .set("sound", settings.sound().toString()) + .set("useSpecialBlocks", settings.useSpecialBlocks().toString()) + .set("showFallMessage", settings.showFallMessage().toString()) + .set("showScoreboard", settings.showScoreboard().toString()) + .set("selectedTime", settings.selectedTime().toString()) + .set("style", settings.style()); + + generator.profileManager.overrideProfile(); + generator.profileManager.calculateChances(); + } + + /** + * Resets every setting of this player to the configured default (or to the option store value when the option is disabled). + */ + public static void applyDefaults(ParkourPlayer player) { + applySettings(player, Map.of()); } public static void applySettings(ParkourPlayer player, Map settings) { for (final String key : COLUMN_MAPPINGS.keySet()) { - final Object value = settings.get(key); final OptionContainer container = COLUMN_MAPPINGS.get(key); if (container.consumer == null) { continue; } + final Object value = settings.get(key); + if (value == null || !OptionGeneral.optionsEnabled().getOrDefault(container.option, true)) { final String defaultValue = OptionGeneral.optionsDefaults().getOrDefault(container.option, ""); container.consumer.accept(player, defaultValue); @@ -95,21 +152,23 @@ public final class PlayerSettingsManager { } private static int parseIntSafe(String v, int fallback) { - if (v == null) { - return fallback; - } - final String s = v.trim(); - if (s.isEmpty() || "true".equalsIgnoreCase(s) || "false".equalsIgnoreCase(s)) { - return fallback; - } try { - return Integer.parseInt(s); + return Integer.parseInt(numericOrFallback(v, String.valueOf(fallback))); } catch (NumberFormatException e) { return fallback; } } private static double parseDoubleSafe(String v, double fallback) { + try { + return Double.parseDouble(numericOrFallback(v, String.valueOf(fallback))); + } catch (NumberFormatException e) { + return fallback; + } + } + + /** Legacy rows kept booleans and empty strings where a number belonged. */ + private static String numericOrFallback(String v, String fallback) { if (v == null) { return fallback; } @@ -117,23 +176,20 @@ public final class PlayerSettingsManager { if (s.isEmpty() || "true".equalsIgnoreCase(s) || "false".equalsIgnoreCase(s)) { return fallback; } - try { - return Double.parseDouble(s); - } catch (NumberFormatException e) { - return fallback; - } + return s; } private static void applyCollectedRewards(ParkourPlayer player, String value) { - player.collectedRewards = new ArrayList<>(); + final List rewards = player.settings().collectedRewards(); + rewards.clear(); if (!value.isEmpty()) { - player.collectedRewards.addAll(Arrays.stream(value.replaceAll("[ \\[\\]]", "").split(",")) + rewards.addAll(Arrays.stream(value.replaceAll("[ \\[\\]]", "").split(",")) .distinct() .toList()); } } - public record OptionContainer(ParkourOption option, BiConsumer consumer) { + record OptionContainer(ParkourOption option, BiConsumer consumer) { } } diff --git a/src/main/java/dev/loki/loparkour/player/service/UserRegistry.java b/src/main/java/dev/loki/loparkour/player/service/UserRegistry.java index 311b872..0389ac3 100644 --- a/src/main/java/dev/loki/loparkour/player/service/UserRegistry.java +++ b/src/main/java/dev/loki/loparkour/player/service/UserRegistry.java @@ -17,6 +17,7 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -37,7 +38,7 @@ public final class UserRegistry { final ParkourUser existing = getUser(player); if (existing != null) { - data = existing.previousData; + data = existing.previousData(); unregister(existing, false, false, false); } @@ -61,11 +62,11 @@ public final class UserRegistry { } public static void unregister(@NotNull ParkourUser user, boolean restorePreviousData, boolean kickIfBungee, boolean urgent) { - if (user.unregistering) { + if (user.unregistering()) { return; } - user.unregistering = true; + user.setUnregistering(true); try { dev.loki.loparkour.adaptive.bootstrap.AdaptiveServices.unloadPlayer(user.getUUID()); new ParkourLeaveEvent(user).call(); @@ -86,7 +87,7 @@ public final class UserRegistry { restorePreviousState(user, urgent); } } finally { - user.unregistering = false; + user.setUnregistering(false); } } @@ -107,6 +108,25 @@ public final class UserRegistry { .collect(Collectors.toSet()); } + public static boolean isPlayer(@Nullable Player player) { + return player != null && getPlayers().stream().anyMatch(other -> other.player == player); + } + + public static @Nullable ParkourPlayer getPlayer(@NotNull Player player) { + // Optimized: search directly in sessions without creating intermediate list + return Divider.SECTIONS.keySet().stream() + .flatMap(session -> session.getPlayers().stream()) + .filter(other -> other.getUUID().equals(player.getUniqueId())) + .findAny() + .orElse(null); + } + + public static List getPlayers() { + return Divider.SECTIONS.keySet().stream() + .flatMap(session -> session.getPlayers().stream()) + .toList(); + } + public static int getJoinCount() { final int count = joinCount; joinCount = 0; @@ -114,9 +134,9 @@ public final class UserRegistry { } private static void resetScoreboard(ParkourUser user) { - if (user.board != null) { + if (user.board() != null) { user.player.setScoreboard(Bukkit.getScoreboardManager().getMainScoreboard()); - user.board = null; + user.setBoard(null); } } @@ -131,16 +151,16 @@ public final class UserRegistry { } private static void restorePreviousState(ParkourUser user, boolean urgent) { - user.previousData.apply(user.player, urgent); + user.previousData().apply(user.player, urgent); - Mode mode = user.session.generator.getMode(); + Mode mode = user.session.generator().getMode(); if (mode == null) { - mode = Modes.DEFAULT; + mode = Modes.defaultMode(); } if (user instanceof ParkourPlayer player) { final Mode finalMode = mode; - user.previousData.onLeave.forEach(r -> r.execute(player, finalMode)); + user.previousData().onLeave.forEach(r -> r.execute(player, finalMode)); } } } diff --git a/src/main/java/dev/loki/loparkour/player/service/ui/ParkourHotbar.java b/src/main/java/dev/loki/loparkour/player/service/ui/ParkourHotbar.java index f83ccba..1a14a35 100644 --- a/src/main/java/dev/loki/loparkour/player/service/ui/ParkourHotbar.java +++ b/src/main/java/dev/loki/loparkour/player/service/ui/ParkourHotbar.java @@ -36,6 +36,6 @@ public final class ParkourHotbar { return; } final int slot = Config.CONFIG.getInt(slotPath); - player.player.getInventory().setItem(slot, Locales.getItem(player.locale, itemPath).build()); + player.player.getInventory().setItem(slot, Locales.getItem(player.locale(), itemPath).build()); } } diff --git a/src/main/java/dev/loki/loparkour/player/service/ui/ScoreboardManager.java b/src/main/java/dev/loki/loparkour/player/service/ui/ScoreboardManager.java index 66e80eb..5c62afd 100644 --- a/src/main/java/dev/loki/loparkour/player/service/ui/ScoreboardManager.java +++ b/src/main/java/dev/loki/loparkour/player/service/ui/ScoreboardManager.java @@ -117,7 +117,7 @@ public class ScoreboardManager { private String replacePlaceholders(String text, Score top, Score high, ParkourGenerator generator) { final String replaced = text - .replace("%score%", Integer.toString(generator.state.score)) + .replace("%score%", Integer.toString(generator.state.score())) .replace("%time%", generator.getFormattedTime()) .replace("%difficulty%", Double.toString(generator.getDifficultyScore())) .replace("%top_score%", Integer.toString(top.score())) diff --git a/src/main/java/dev/loki/loparkour/player/spectator/ParkourSpectator.java b/src/main/java/dev/loki/loparkour/player/spectator/ParkourSpectator.java index 6127508..7124859 100644 --- a/src/main/java/dev/loki/loparkour/player/spectator/ParkourSpectator.java +++ b/src/main/java/dev/loki/loparkour/player/spectator/ParkourSpectator.java @@ -35,7 +35,7 @@ public class ParkourSpectator extends ParkourUser { * The closest player. */ @NotNull - public ParkourPlayer closest; + private ParkourPlayer closest; public ParkourSpectator(@NotNull Player player, @NotNull Session session, @Nullable PreviousData previousData) { super(player, session, previousData); @@ -79,7 +79,7 @@ public class ParkourSpectator extends ParkourUser { final String actionBar = ColorUtil.color(Locales.getString(player, "play.spectator.action_bar")); player.spigot().sendMessage(ChatMessageType.ACTION_BAR, TextComponent.fromLegacyText(actionBar)); player.setGameMode(GameMode.SPECTATOR); - updateScoreboard(session.generator); + updateScoreboard(session.generator()); // spectator is still being teleported to world if (closest.getLocation().getWorld() != player.getLocation().getWorld()) { @@ -100,6 +100,16 @@ public class ParkourSpectator extends ParkourUser { player.setSpectatorTarget(player.getSpectatorTarget()); } + /** + * Player this spectator is currently following. + * + * @return The closest parkour player. + */ + @NotNull + public ParkourPlayer closest() { + return closest; + } + /** * Stops the closest checker runnable. */ diff --git a/src/main/java/dev/loki/loparkour/reward/core/Reward.java b/src/main/java/dev/loki/loparkour/reward/core/Reward.java index 3f9ca1b..cf478d9 100644 --- a/src/main/java/dev/loki/loparkour/reward/core/Reward.java +++ b/src/main/java/dev/loki/loparkour/reward/core/Reward.java @@ -26,48 +26,48 @@ public record Reward(@NotNull String string) { return; } - String string = this.string; + String command = this.string; final Mode rewardMode = Registry.getModes().stream() .filter(m -> this.string.contains("%s:".formatted(m.getName().toLowerCase()))) .findFirst() - .orElse(Modes.DEFAULT); + .orElse(Modes.defaultMode()); if (mode != rewardMode) { return; } - string = string.replaceFirst("%s:".formatted(rewardMode.getName().toLowerCase()), ""); + command = command.replaceFirst("%s:".formatted(rewardMode.getName().toLowerCase()), ""); // check for placeholders - if (string.toLowerCase().contains("%player%")) { - string = string.replaceAll("%player%", player.getName()); + if (command.toLowerCase().contains("%player%")) { + command = command.replaceAll("%player%", player.getName()); } // check for extra data - if (string.toLowerCase().contains("leave:")) { // leave: - string = string.replaceFirst("leave:", ""); - player.previousData.onLeave.add(new Reward(string)); + if (command.toLowerCase().contains("leave:")) { // leave: + command = command.replaceFirst("leave:", ""); + player.previousData().onLeave.add(new Reward(command)); return; } // check for things to perform - if (string.toLowerCase().contains("send:")) { - string = string.replaceFirst("send:", ""); + if (command.toLowerCase().contains("send:")) { + command = command.replaceFirst("send:", ""); - player.send(string); - } else if (string.toLowerCase().contains("vault:")) { - string = string.replaceFirst("vault:", ""); + player.send(command); + } else if (command.toLowerCase().contains("vault:")) { + command = command.replaceFirst("vault:", ""); try { - VaultHook.deposit(player.player, Double.parseDouble(string)); + VaultHook.deposit(player.player, Double.parseDouble(command)); } catch (NumberFormatException ex) { LoParkour.getPlugin().getLogger().severe( "Error while trying to process Vault reward - check your rewards file for incorrect numbers - " + ex.getMessage()); } } else { - Bukkit.dispatchCommand(Bukkit.getConsoleSender(), string); + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command); } } } diff --git a/src/main/java/dev/loki/loparkour/reward/core/Rewards.java b/src/main/java/dev/loki/loparkour/reward/core/Rewards.java index 6e7ffd4..f860537 100644 --- a/src/main/java/dev/loki/loparkour/reward/core/Rewards.java +++ b/src/main/java/dev/loki/loparkour/reward/core/Rewards.java @@ -3,6 +3,7 @@ package dev.loki.loparkour.reward.core; import dev.loki.loparkour.LoParkour; import dev.loki.loparkour.config.core.Config; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -15,40 +16,42 @@ public final class Rewards { private Rewards() { } - public static boolean REWARDS_ENABLED; + private static boolean rewardsEnabled; /** * A map with all Score-type score rewards. * The key is the score, and the value are the commands that will be executed once this score is reached. */ - public static Map> SCORE_REWARDS = new HashMap<>(); + private static final Map> SCORE_REWARDS = new HashMap<>(); /** * A map with all Interval-type score rewards. * The key is the score, and the value are the commands that will be executed once this score is reached. */ - public static Map> INTERVAL_REWARDS = new HashMap<>(); + private static final Map> INTERVAL_REWARDS = new HashMap<>(); /** * A map with all One time-type score rewards. * The key is the score, and the value are the commands that will be executed once this score is reached. */ - public static Map> ONE_TIME_REWARDS = new HashMap<>(); + private static final Map> ONE_TIME_REWARDS = new HashMap<>(); /** * Reads the rewards from the rewards-v2.yml file */ public static void init() { // init options - REWARDS_ENABLED = Config.REWARDS.getBoolean("enabled"); + rewardsEnabled = Config.REWARDS.getBoolean("enabled"); - if (!REWARDS_ENABLED) { + clear(); + + if (!rewardsEnabled) { return; } - SCORE_REWARDS = parseScores("score-rewards"); - INTERVAL_REWARDS = parseScores("interval-rewards"); - ONE_TIME_REWARDS = parseScores("one-time-rewards"); + SCORE_REWARDS.putAll(parseScores("score-rewards")); + INTERVAL_REWARDS.putAll(parseScores("interval-rewards")); + ONE_TIME_REWARDS.putAll(parseScores("one-time-rewards")); } /** @@ -60,6 +63,42 @@ public final class Rewards { ONE_TIME_REWARDS.clear(); } + /** + * Whether the reward system is active. + * + * @return True when the rewards system is enabled in rewards-v2.yml. + */ + public static boolean enabled() { + return rewardsEnabled; + } + + /** + * Score rewards. + * + * @return Read-only view of the score rewards, keyed by score. + */ + public static Map> scoreRewards() { + return Collections.unmodifiableMap(SCORE_REWARDS); + } + + /** + * Interval rewards. + * + * @return Read-only view of the interval rewards, keyed by score. + */ + public static Map> intervalRewards() { + return Collections.unmodifiableMap(INTERVAL_REWARDS); + } + + /** + * One-time rewards. + * + * @return Read-only view of the one-time rewards, keyed by score. + */ + public static Map> oneTimeRewards() { + return Collections.unmodifiableMap(ONE_TIME_REWARDS); + } + private static Map> parseScores(String path) { final Map> rewardMap = new HashMap<>(); diff --git a/src/main/java/dev/loki/loparkour/schematic/config/SchematicDifficultyConfig.java b/src/main/java/dev/loki/loparkour/schematic/config/SchematicDifficultyConfig.java index cd9b28d..46148b5 100644 --- a/src/main/java/dev/loki/loparkour/schematic/config/SchematicDifficultyConfig.java +++ b/src/main/java/dev/loki/loparkour/schematic/config/SchematicDifficultyConfig.java @@ -5,6 +5,7 @@ import dev.loki.loparkour.schematic.core.SchematicManager; import org.bukkit.configuration.file.YamlConfiguration; import org.jetbrains.annotations.NotNull; +import java.io.File; import java.io.IOException; /** @@ -16,16 +17,16 @@ public final class SchematicDifficultyConfig { } public static void setDifficulty(@NotNull String fileStem, double difficulty) throws IOException { - if (Config.SCHEMATICS.path == null) { + final File configFile = Config.SCHEMATICS.path(); + if (configFile == null) { throw new IOException("Schematics config path is not initialized"); } - final YamlConfiguration yaml = YamlConfiguration.loadConfiguration(Config.SCHEMATICS.path); + final YamlConfiguration yaml = YamlConfiguration.loadConfiguration(configFile); final String key = SchematicManager.configKey(fileStem); yaml.set("difficulty." + key, difficulty); - yaml.save(Config.SCHEMATICS.path); + yaml.save(configFile); - Config.SCHEMATICS.fileConfiguration = YamlConfiguration.loadConfiguration(Config.SCHEMATICS.path); Config.SCHEMATICS.load(); } } diff --git a/src/main/java/dev/loki/loparkour/schematic/core/SchematicManager.java b/src/main/java/dev/loki/loparkour/schematic/core/SchematicManager.java index 703d5e8..782c05c 100644 --- a/src/main/java/dev/loki/loparkour/schematic/core/SchematicManager.java +++ b/src/main/java/dev/loki/loparkour/schematic/core/SchematicManager.java @@ -171,9 +171,10 @@ public final class SchematicManager { } private static double resolveDifficulty(@NotNull String fileStem) { - final ConfigurationSection section = Config.SCHEMATICS.fileConfiguration == null + final var config = Config.SCHEMATICS.fileConfiguration(); + final ConfigurationSection section = config == null ? null - : Config.SCHEMATICS.fileConfiguration.getConfigurationSection("difficulty"); + : config.getConfigurationSection("difficulty"); if (section == null) { return DEFAULT_DIFFICULTY; } diff --git a/src/main/java/dev/loki/loparkour/session/core/Session.java b/src/main/java/dev/loki/loparkour/session/core/Session.java index b95f957..b0024f7 100644 --- a/src/main/java/dev/loki/loparkour/session/core/Session.java +++ b/src/main/java/dev/loki/loparkour/session/core/Session.java @@ -21,7 +21,7 @@ import java.util.function.Function; */ public final class Session { - public ParkourGenerator generator; + private ParkourGenerator generator; private final SessionPlayerManager playerManager; private final SessionStateManager stateManager; @@ -65,8 +65,8 @@ public final class Session { session.stateManager.initialize(generatorFunction); // Update generator settings for players - if (session.generator != null) { - parkourPlayers.forEach(p -> p.updateGeneratorSettings(session.generator)); + if (session.generator() != null) { + parkourPlayers.forEach(p -> p.updateGeneratorSettings(session.generator())); } return session; @@ -142,6 +142,15 @@ public final class Session { stateManager.cleanup(); } + /** + * Generator driving this session. + * + * @return The generator, null until the session state is initialized. + */ + public ParkourGenerator generator() { + return generator; + } + public void setGenerator(@NotNull ParkourGenerator generator) { this.generator = generator; } diff --git a/src/main/java/dev/loki/loparkour/session/manager/SessionStateManager.java b/src/main/java/dev/loki/loparkour/session/manager/SessionStateManager.java index 1f6b7b3..7bef4bd 100644 --- a/src/main/java/dev/loki/loparkour/session/manager/SessionStateManager.java +++ b/src/main/java/dev/loki/loparkour/session/manager/SessionStateManager.java @@ -58,8 +58,8 @@ public class SessionStateManager { // Stop tick cycle stopTickCycle(); - if (session.generator != null) { - session.generator.reset(false); + if (session.generator() != null) { + session.generator().reset(false); } Divider.remove(session); @@ -70,19 +70,19 @@ public class SessionStateManager { * Start tick cycle for generator. */ private void startTickCycle() { - if (session.generator == null || tickTask != null) { + if (session.generator() == null || tickTask != null) { return; } tickTask = Scheduler.get(LoParkour.getPlugin()).runTimer(() -> { - if (session.generator != null) { + if (session.generator() != null) { try { - session.generator.tick(); + session.generator().tick(); if (LoParkour.getGhostService() != null) { - LoParkour.getGhostService().recordPlayers(session.generator); + LoParkour.getGhostService().recordPlayers(session.generator()); } } catch (Throwable t) { - session.generator.session.getPlayers().forEach(p -> + session.generator().session.getPlayers().forEach(p -> p.player.sendMessage("§c[LoParkour] Tick error: " + t.getMessage())); } } diff --git a/src/main/java/dev/loki/loparkour/session/manager/SessionUserManager.java b/src/main/java/dev/loki/loparkour/session/manager/SessionUserManager.java index 85fa4d9..4fcedd3 100644 --- a/src/main/java/dev/loki/loparkour/session/manager/SessionUserManager.java +++ b/src/main/java/dev/loki/loparkour/session/manager/SessionUserManager.java @@ -92,14 +92,14 @@ class SessionUserManager { private void notifyJoin(ParkourPlayer player) { for (final ParkourPlayer to : getPlayers()) { - to.send(Locales.getString(player.locale, "lobby.other_join").formatted(player.getName())); + to.send(Locales.getString(player.locale(), "lobby.other_join").formatted(player.getName())); } } private void notifyLeave(ParkourPlayer[] toRemove, List remainingPlayers) { for (final ParkourPlayer player : toRemove) { for (final ParkourPlayer to : remainingPlayers) { - to.send(Locales.getString(player.locale, "lobby.other_leave").formatted(player.getName())); + to.send(Locales.getString(player.locale(), "lobby.other_leave").formatted(player.getName())); } } } diff --git a/src/main/java/dev/loki/loparkour/storage/disk/StorageDisk.java b/src/main/java/dev/loki/loparkour/storage/disk/StorageDisk.java index cfc873f..2e784c4 100644 --- a/src/main/java/dev/loki/loparkour/storage/disk/StorageDisk.java +++ b/src/main/java/dev/loki/loparkour/storage/disk/StorageDisk.java @@ -1,15 +1,18 @@ package dev.loki.loparkour.storage.disk; import com.google.gson.annotations.Expose; +import com.google.gson.reflect.TypeToken; import dev.loki.loparkour.LoParkour; import dev.loki.loparkour.leaderboard.model.Score; import dev.loki.loparkour.player.core.ParkourPlayer; +import dev.loki.loparkour.player.service.PlayerSettingsManager; import org.jetbrains.annotations.NotNull; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; +import java.lang.reflect.Type; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; @@ -25,6 +28,14 @@ public final class StorageDisk { private StorageDisk() { } + private static final Type SETTINGS_TYPE = new TypeToken>() { }.getType(); + + /** Files written before the settings had their own object named three of them differently. */ + private static final Map LEGACY_KEYS = Map.of( + "particles", "useParticles", + "useSpecialBlocks", "useSpecial", + "showFallMessage", "showFallMsg"); + public static @NotNull Map readScores(@NotNull String mode) { final File file = getLeaderboardFile(mode); @@ -77,56 +88,46 @@ public final class StorageDisk { } public static void readPlayer(@NotNull ParkourPlayer player) { - if (!getPlayerFile(player).exists()) { - player.setSettings(new HashMap<>()); + final File file = getPlayerFile(player); + + if (!file.exists()) { + PlayerSettingsManager.applyDefaults(player); return; } - try (FileReader reader = new FileReader(getPlayerFile(player))) { - final ParkourPlayer from = LoParkour.getGson().fromJson(reader, ParkourPlayer.class); + try (FileReader reader = new FileReader(file)) { + final Map settings = LoParkour.getGson().fromJson(reader, SETTINGS_TYPE); - if (from == null) { + if (settings == null) { LoParkour.getPlugin().getLogger().warning( "Player data file for %s is empty or corrupted, using defaults".formatted(player.getName())); - player.setSettings(new HashMap<>()); + PlayerSettingsManager.applyDefaults(player); return; } - final Map settings = new HashMap<>(); - - settings.put("style", from.style); - settings.put("blockLead", from.blockLead); - settings.put("useParticles", from.particles); - settings.put("useSpecial", from.useSpecialBlocks); - settings.put("showFallMsg", from.showFallMessage); - settings.put("showScoreboard", from.showScoreboard); - settings.put("selectedTime", from.selectedTime); - settings.put("ghostRecording", from.ghostRecording); - settings.put("collectedRewards", from.collectedRewards); - - // Sanitize legacy Boolean locale values - String locale = from.locale; - if (locale == null || "true".equals(locale) || "false".equals(locale) || "1".equals(locale) || "0".equals(locale)) { - locale = "en"; - } - settings.put("locale", locale); - settings.put("schematicDifficulty", from.schematicDifficulty); - settings.put("sound", from.sound); - - player.setSettings(settings); + normalizeLegacyKeys(settings); + PlayerSettingsManager.applySettings(player, settings); } catch (IOException ex) { LoParkour.getPlugin().getLogger().severe( "Error while trying to read disk data of %s".formatted(player.getName()) + " - " + ex.getMessage()); } } + private static void normalizeLegacyKeys(Map settings) { + LEGACY_KEYS.forEach((legacy, key) -> { + if (!settings.containsKey(key) && settings.containsKey(legacy)) { + settings.put(key, settings.remove(legacy)); + } + }); + } + public static void writePlayer(@NotNull ParkourPlayer player) { final File file = getPlayerFile(player); createFile(file); try (FileWriter writer = new FileWriter(file)) { - LoParkour.getGson().toJson(player, writer); + LoParkour.getGson().toJson(PlayerSettingsManager.toSettingsMap(player), writer); writer.flush(); } catch (IOException ex) { LoParkour.getPlugin().getLogger().severe( diff --git a/src/main/java/dev/loki/loparkour/storage/sql/SQLDataMapper.java b/src/main/java/dev/loki/loparkour/storage/sql/SQLDataMapper.java index 9f9e008..79edca1 100644 --- a/src/main/java/dev/loki/loparkour/storage/sql/SQLDataMapper.java +++ b/src/main/java/dev/loki/loparkour/storage/sql/SQLDataMapper.java @@ -4,6 +4,7 @@ import com.google.gson.reflect.TypeToken; import dev.loki.loparkour.LoParkour; import dev.loki.loparkour.leaderboard.model.Score; import dev.loki.loparkour.player.core.ParkourPlayer; +import dev.loki.loparkour.player.service.PlayerSettingsManager; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -89,8 +90,8 @@ public final class SQLDataMapper { public static void setPlayerParameters(@NotNull PreparedStatement stmt, @NotNull ParkourPlayer player) throws SQLException { stmt.setString(1, player.player.getUniqueId().toString()); stmt.setString(2, player.player.getName()); - stmt.setString(LOCALE_PARAM_INDEX, player.locale); - stmt.setString(STYLE_PARAM_INDEX, player.style); + stmt.setString(LOCALE_PARAM_INDEX, player.locale()); + stmt.setString(STYLE_PARAM_INDEX, player.settings().style()); stmt.setString(SETTINGS_PARAM_INDEX, serializeSettings(player)); stmt.setLong(TIMESTAMP_PARAM_INDEX, System.currentTimeMillis()); } @@ -99,49 +100,31 @@ public final class SQLDataMapper { * Apply player data to ParkourPlayer object. */ public static void applyPlayerData(@NotNull ParkourPlayer player, @NotNull PlayerData data) { - // Sanitize legacy Boolean locale values - String locale = data.locale(); - if (locale == null || "true".equals(locale) || "false".equals(locale) || "1".equals(locale) || "0".equals(locale)) { - locale = "en"; - } - player.locale = locale; - player.style = data.styleName(); + player.setLocale(PlayerSettingsManager.sanitizeLocale(data.locale())); + player.settings().setStyle(data.styleName()); // Apply settings if available if (data.settings() != null && !data.settings().isEmpty()) { deserializeSettings(player, data.settings()); } else { - player.setSettings(new HashMap<>()); + PlayerSettingsManager.applyDefaults(player); } } @NotNull private static String serializeSettings(@NotNull ParkourPlayer player) { - final Map settings = new HashMap<>(); - settings.put("style", player.style); - settings.put("blockLead", player.blockLead); - settings.put("useParticles", player.particles); - settings.put("useSpecial", player.useSpecialBlocks); - settings.put("showFallMsg", player.showFallMessage); - settings.put("showScoreboard", player.showScoreboard); - settings.put("selectedTime", player.selectedTime); - settings.put("ghostRecording", player.ghostRecording); - settings.put("collectedRewards", player.collectedRewards); - settings.put("locale", player.locale); - settings.put("schematicDifficulty", player.schematicDifficulty); - settings.put("sound", player.sound); - return LoParkour.getGson().toJson(settings); + return LoParkour.getGson().toJson(PlayerSettingsManager.toSettingsMap(player)); } private static void deserializeSettings(@NotNull ParkourPlayer player, @NotNull String settings) { final Type type = new TypeToken>() { }.getType(); final Map parsed = LoParkour.getGson().fromJson(settings, type); if (parsed == null) { - player.setSettings(new HashMap<>()); + PlayerSettingsManager.applyDefaults(player); return; } normalizeNumericSettings(parsed); - player.setSettings(parsed); + PlayerSettingsManager.applySettings(player, parsed); } private static void normalizeNumericSettings(@NotNull Map settings) { diff --git a/src/main/java/dev/loki/loparkour/storage/sql/repo/SQLPlayerRepository.java b/src/main/java/dev/loki/loparkour/storage/sql/repo/SQLPlayerRepository.java index 49156d9..3fa2234 100644 --- a/src/main/java/dev/loki/loparkour/storage/sql/repo/SQLPlayerRepository.java +++ b/src/main/java/dev/loki/loparkour/storage/sql/repo/SQLPlayerRepository.java @@ -1,6 +1,7 @@ package dev.loki.loparkour.storage.sql.repo; import dev.loki.loparkour.player.core.ParkourPlayer; +import dev.loki.loparkour.player.service.PlayerSettingsManager; import org.jetbrains.annotations.NotNull; import java.sql.Connection; @@ -39,7 +40,7 @@ public final class SQLPlayerRepository { if (data != null) { SQLDataMapper.applyPlayerData(player, data); } else { - player.setSettings(new java.util.HashMap<>()); + PlayerSettingsManager.applyDefaults(player); } } diff --git a/src/main/java/dev/loki/loparkour/util/particle/ParticleData.java b/src/main/java/dev/loki/loparkour/util/particle/ParticleData.java index 34614d6..072df5a 100644 --- a/src/main/java/dev/loki/loparkour/util/particle/ParticleData.java +++ b/src/main/java/dev/loki/loparkour/util/particle/ParticleData.java @@ -22,28 +22,28 @@ public class ParticleData { this.size = size; } - public ParticleData size(int size) { - this.size = size; + public ParticleData size(int value) { + size = value; return this; } - public ParticleData speed(double speed) { - this.speed = speed; + public ParticleData speed(double value) { + speed = value; return this; } - public ParticleData offsetX(double offsetX) { - this.offsetX = offsetX; + public ParticleData offsetX(double value) { + offsetX = value; return this; } - public ParticleData offsetY(double offsetY) { - this.offsetY = offsetY; + public ParticleData offsetY(double value) { + offsetY = value; return this; } - public ParticleData offsetZ(double offsetZ) { - this.offsetZ = offsetZ; + public ParticleData offsetZ(double value) { + offsetZ = value; return this; } diff --git a/src/main/java/dev/loki/loparkour/world/core/World.java b/src/main/java/dev/loki/loparkour/world/core/World.java index 58a0121..628bb04 100644 --- a/src/main/java/dev/loki/loparkour/world/core/World.java +++ b/src/main/java/dev/loki/loparkour/world/core/World.java @@ -38,22 +38,22 @@ public final class World { return; } - final var world = Bukkit.getWorld(name); + final var existingWorld = Bukkit.getWorld(name); if (!Config.CONFIG.getBoolean("joining")) { return; } - if (world != null) { + if (existingWorld != null) { LoParkour.getPlugin().getLogger().warning( "Crash detected! The parkour world loading twice is not usual behaviour. " + "This only happens after a server crash."); - World.world = world; // ensure static field is populated + world = existingWorld; // ensure static field is populated } if (Config.CONFIG.getBoolean("world.delete-on-reload")) { deleteWorld(); - } else if (world != null) { + } else if (existingWorld != null) { // Already loaded (e.g. pre-created by a worlds plugin, required on Folia) — do not recreate. LoParkour.log("Using already loaded world " + name); setup();