From c1203096635b2c6e2973fd377647646ed12eb0af Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Tue, 6 Oct 2026 15:54:31 +0200 Subject: [PATCH] refactor(config): encapsulate option sections, drop the Option mirror Option was a facade of 45 public static fields kept in sync with the four sections by syncFields(); the sections themselves also exposed public static mutable fields. 88 VisibilityModifier violations in one place and two owners for every value. - Option keeps only init(boolean) (the reload entry ConfigLoader calls); the mirrors, syncFields() and the unused @Deprecated initStyles are gone - sections hold private static state behind fluent accessors (OptionSQL.enabled(), OptionGeneration.cleanupInterval(), OptionGeneral.goBackLocation()); the two Option.SQL = false writes become OptionSQL.disable() - collection/location accessors hand out unmodifiable views and a Location copy, so no caller can mutate loaded config state - ParticleShape promoted from nested enum to config/options/ParticleShape - delete 17 never-read generation probability fields and initGenerationProbabilities(): live weights come from advanced.* via GeneratorProfileManager/GenerationProfileLoader; min-y/max-y guard moved 1:1 into initBounds() - rewrite 63 Option. reads in 19 files to the section accessors - validateConfigs() now checks the paths the code actually consumes (advanced.distance/height/type weights) instead of the dead generation.normal.* - ParkourRestrictionListener.onChat: getOrDefault(CHAT, true) instead of unboxing a Boolean that may be absent checkstyle 0 errors / 329 -> 240 warnings (VisibilityModifier 163 -> 75); config/options itself is clean. Dead config keys found on the way are filed as TODO items 164-166. ./gradlew check green: PMD 0, 94 tests, NullAway unchanged. --- TODO.md | 47 +++-- .../adaptive/bootstrap/AdaptiveServices.java | 8 +- .../loparkour/bootstrap/PluginBootstrap.java | 4 +- .../command/schematic/wand/SchematicWand.java | 4 +- .../loparkour/config/core/ConfigLoader.java | 36 +++- .../loki/loparkour/config/locale/Locales.java | 10 +- .../loki/loparkour/config/options/Option.java | 148 +--------------- .../config/options/OptionGeneral.java | 70 +++++--- .../config/options/ParticleShape.java | 13 ++ .../options/section/OptionGeneration.java | 167 ++++++++++-------- .../options/section/OptionParticles.java | 56 ++++-- .../config/options/section/OptionSQL.java | 62 +++++-- .../loki/loparkour/duels/DuelsGenerator.java | 4 +- .../generator/core/model/Island.java | 4 +- .../jump/calculation/JumpValidator.java | 10 +- .../lifecycle/loop/LifecycleTickManager.java | 4 +- .../lifecycle/player/GeneratorCleanup.java | 7 +- .../gameplay/ParkourRestrictionListener.java | 4 +- .../dev/loki/loparkour/menu/core/LPMenu.java | 4 +- .../menu/core/screen/ParkourOption.java | 4 +- .../loparkour/player/core/ParkourUser.java | 6 +- .../loparkour/player/data/PreviousData.java | 4 +- .../player/service/PlayerSettingsManager.java | 14 +- .../dev/loki/loparkour/storage/Storage.java | 16 +- .../storage/sql/SQLConnectionManager.java | 12 +- .../sql/schema/SQLMigrationManager.java | 10 +- .../loki/loparkour/world/core/Divider.java | 17 +- 27 files changed, 386 insertions(+), 359 deletions(-) create mode 100644 src/main/java/dev/loki/loparkour/config/options/ParticleShape.java diff --git a/TODO.md b/TODO.md index f882080..c3e5dcb 100644 --- a/TODO.md +++ b/TODO.md @@ -112,10 +112,10 @@ - **60. `SQLDataMapper` UUID.fromString кидает unchecked IllegalArgumentException** (SQLDataMapper.java:65,31) мимо catch(SQLException) → битые строки роняют чтение. → Ловить и логировать/скипать строку. - **61. `SQLQueryExecutor.prepareStatement`** (без callers) никогда не закрывает connection → утечка пула при будущем использовании. → try-with-resources / borrow-tracking. -- **62. `SQLMigrationManager` мёртв; `migration_adaptive.sql` ссылается на несуществующую `loparkour_scores` и нигде не исполняется**; та же DDL в 3 копиях; префикс `SQL_PREFIX` применён непоследовательно. → Единый мигратор с версионированием; починить/удалить SQL-файл. +- **62. `SQLMigrationManager` мёртв; `migration_adaptive.sql` ссылается на несуществующую `loparkour_scores` и нигде не исполняется**; та же DDL в 3 копиях; префикс `OptionSQL.prefix()` применён непоследовательно. → Единый мигратор с версионированием; починить/удалить SQL-файл. - **63. `writeScores` гоняет всю таблицу туда-сюда** без дельт (StorageSQL.java:105-128). → Инкрементальные upsert'ы только изменённых записей. - **64. `Score.fromString` — CSV по запятой: ник с `,` ломает все поля** (Score.java:19-31). → Экранирование или JSON. -- **65. `validateConnection` мутирует `Option.SQL=false` при транзиентном сбое** (SQLConnectionManager.java:81-86) — SQL отключается навсегда. → Отдельный флаг/ретрай. +- **65. `validateConnection` мутирует `OptionSQL.disable()` при транзиентном сбое** (SQLConnectionManager.java:87-92) — SQL отключается навсегда. → Отдельный флаг/ретрай. ### Adaptive @@ -126,11 +126,14 @@ ### Config / reload -- **70. `/lp reload` не атомарен**: дублирует `Registry.STYLES` (никогда не чистится), оставляет старые `Rewards` при `enabled:false`, `BORDER_SIZE` устаревший, миры/генераторы не обновляются (ConfigUpdater, OptionStyles.java:23-43, Rewards.java:41-52, OptionGeneral.java:38-40). → Полный reset registry + пересоздание зависимых подсистем. +- **70. `/lp reload` не атомарен**: дублирует `Registry.STYLES` (никогда не чистится), оставляет старые `Rewards` при `enabled:false`, `OptionGeneral.borderSize()` устаревший (перечитывается только при `firstLoad`), миры/генераторы не обновляются (ConfigUpdater, OptionStyles.java:23-43, Rewards.java:41-52, OptionGeneral.java:44-50). → Полный reset registry + пересоздание зависимых подсистем. - **71. `LocaleLoader` при ошибке подменяет кэш на emptyMap** → все сообщения пустые (LocaleCache.java:22-25). → Сохранять старый кэш при сбое. - **72. Нет fallback на en** для пропущенных ключей/неизвестных локалей (Locales.java:69-71). → Fallback-цепочка locale → en. - **73. `Rewards.init()` при `enabled:false` не чистит статические мапы** → старые награды продолжают выдаваться после reload. → Очистка. - **74. `ConfigAccessor` типо-опечатка тихо даёт false/0** (ConfigAccessor.java:45-48). → Строгая валидация ключей при старте. +- **164. Секция `generation.*` в generation.yml игнорируется.** Живые веса берутся из `advanced.distance|height|type|special` (`GeneratorProfileManager`, `GenerationProfileLoader`), а `generation.type|normal|special` читал только мёртвый `OptionGeneration.initGenerationProbabilities()` — удалён на шаге инкапсуляции `config/options`. В shipped `generation.yml` обе секции дублируют друг друга (значения разные: 85/10/5 против 80/15/5) → пользователь правит то, что не работает. → Либо удалить `generation.type|normal|special` из generation.yml, либо сделать их источником для профилей; `generation.settings.min-y/max-y` остаются живыми. `ConfigLoader.validateConfigs` уже проверяет живые пути `advanced.*`. +- **165. Мёртвый конфиг частиц/звуков.** `particles.sound-type|sound-pitch|sound-volume|particle-type|particle-shape` читаются в `OptionParticles`, все 6 геттеров (`shape()`, `soundType()`, `particleData()`…) не вызываются нигде → настройки не влияют на игру (используются per-player `ParkourPlayer.particles/sound` из меню). → Подключить в рендере частиц/звуков (`util/particle/`, `HookSound`) или убрать секцию из config.yml. +- **166. Ещё два читаемых, но не потребляемых ключа.** `options.leads.amount` → `OptionGeneral.possibleLeads()` (валидация 1–128 и лог работают, список не читает никто — lead берётся из `default-values.leads.default`), `memory.block-cleanup-distance` → `OptionGeneration.blockCleanupDistance()` (дистанция чистки захардкожена в `GeneratorCleanup`). → Подключить или удалить. ### Session @@ -169,7 +172,7 @@ - **95. `SchematicIdGenerator` хеширует только byte-координаты → коллизии id** (SchematicIdGenerator.java:23-28). - **96. `parseDifficulty` принимает NaN/Infinity** (SchematicCreator.java:64-70) → схема навсегда невыбираема. - **97. `stem()` регистрирует файл `.nbt` под пустым id**; `.NBT` игнорируется, `.schematic` — case-insensitive (SchematicManager.java:147-150,88). -- **98. `Locales.getPlayerLocale`/`ParkourUser.locale` deref `OPTIONS_DEFAULTS.get(LANG)` без null-check** (Locales.java:170-184, ParkourUser.java:64). +- **98. `Locales.getPlayerLocale`/`ParkourUser.locale` deref `OptionGeneral.optionsDefaults().get(LANG)` без null-check** (Locales.java:170-184, ParkourUser.java:64). - **99. Магические числа**: fall-threshold -30 (LifecycleTickManager.java:72), render 24.0/0.1/%4 (InvisibleBarrierMode.java:67-68,98), 10/30 (GravityShiftMode.java:82,108), 100 тиков (RaceMode.java:147), hotbar delay 5 (ParkourPlayer.java:192), `TICKS_PER_SECOND=20` переопределён (SpeedrunMode.java:82). → В конфиг/константы. - **100. Deprecated API**: `Material.STONE_SLAB` (MaterialUtil.java:20), `registerNewObjective(String,String,String)` (ScoreboardManager.java:75), `getOfflinePlayer(name)` (AdminCommandHandler.java:186-199), legacy ACTION_BAR (ParkourSpectator.java:73, RaceMode.java:103). - **101. `ParkourSpectator.java:96` — бессмысленный double-set spectator target** (no-op). @@ -427,14 +430,38 @@ Checkstyle отработал `0 errors` (error-уровневые legacy-нар > Изменено 45 файлов (TODO.md, 2 конфига линтеров, 46 исходников). > > **Осталось (порядок):** -> 1. Тест-гейт «**в папке ≤ 4 файлов**» (JUnit, без новых зависимостей) + разбивка 6 переполненных пакетов: -> `elytra/generator` 7, `elytra/mode` 6, `command/schematic` 6, `player/core` 5, `generator/jump/placement` 5, `generator/jump/calculation` 5 — -> 10 файлов переносятся в подпапки (механически: package + импорты). -> 2. Пакет за пакетом обнулять бэклог, начиная с `config/options` (91 нарушение в 2 файлах: `Option` — 45 public-полей, -> `OptionGeneration` — 25; это и есть п.77/107 про public mutable state) → после обнуления перевести правило в error. +> 1. ✅ Тест-гейт «**в папке ≤ 4 файлов**» (JUnit, без новых зависимостей) + разбивка 6 переполненных пакетов — +> сделано в `4263df5`, `FOLDER_DEBT = Set.of()`, ни одна папка `src/main/java` не содержит >4 файлов. +> 2. ✅ Бэклог `config/options` обнулён (см. ПРОГРЕСС-5 ниже) → **`VisibilityModifier` ещё НЕ в error**: в пакете остались +> только 2 `IllegalCatch` (общий бэклог п.3), а правило висит на 75 нарушениях в других пакетах. > 3. `IllegalCatch` 23: 3 сайта намеренные (команда/конфиг с откатом/скетчи — boundary-catch), остальные 20 — выловить конкретные типы; > **нужно решение владельца** по трём. -> 4. Дальше по плану (a): PMD-категории целиком, CPD, SpotBugs, ArchUnit-слои, JaCoCo, Spotless, quality-job в CI. +> 4. Дальше по плану (a): PMD-категории целиком, CPD, SpotBugs, ArchUnit-слои, JaCoCo, Spotless, quality-job в CI — ✅ сделано в `b771cc1`. +> +> ## ✅ ПРОГРЕСС-5 (Задача 2, шаг (c): `config/options` инкапсулирован, 2026-10-06) +> Был анти-паттерн: `Option` — фасад-зеркало на **45 `public static` полей**, которые `syncFields()` копировал +> из 4 секций (`OptionSQL`, `OptionParticles`, `OptionGeneration`, `OptionGeneral`), и в самих секциях поля тоже +> были `public static` (мутабельное состояние чужих пакетов = п.77/107 в миниатюре). +> - **`Option` сокращён до `init(boolean)`** — единственная точка перезагрузки конфигов (её зовёт `ConfigLoader`); +> зеркало, `syncFields()` и мёртвый `@Deprecated initStyles` (0 вызовов) удалены. Класс больше не дублирует секции. +> - **Секции: `private static` поля + fluent-аксессоры** (`OptionSQL.enabled()`, `OptionGeneration.cleanupInterval()`, +> `OptionGeneral.goBackLocation()`…), без префикса `get` — как у соседних статик-холдеров (`Materials.parse`, `Probs.random`). +> Две точки записи `Option.SQL = false` → `OptionSQL.disable()`. +> - **Защита от протечки состояния:** `possibleLeads()/optionsEnabled()/optionsDefaults()` отдают unmodifiable-представления +> (null сохраняется как раньше), `goBackLocation()` — копию (`Location` мутируется при reload через `setPitch/setYaw`). +> - `ParticleShape` вынесен из вложенного enum'а в `config/options/ParticleShape.java` (в `section/` леж бы 5-й файл — гейт «≤4»). +> - **Удалены мёртвые данные:** 17 полей вероятностей генерации + `initGenerationProbabilities()` — не читались нигде, +> живые веса берутся из `advanced.*` (`GeneratorProfileManager`/`GenerationProfileLoader`). Границы `min-y/max-y` +> с проверкой `minY >= maxY` перенесены 1-в-1 в `initBounds()`. Найденное оформлено пунктами **164–166**. +> - **63 обращения `Option.<ПОЛЕ>`** (не 127 — счёт предыдущей сессии был завышен из-за подстроки в `ParkourOption.*`) +> переписаны в **19 файлах**; `Option`-импорт остался только в `ConfigLoader`. +> - Попутно (предложения `CodeReview`): `ConfigLoader.validateConfigs()` проверяет **живые** пути +> `advanced.distance.chances|height.chances|height.min|max|type` (раньше проверялась мёртвая секция `generation.normal.height.*`); +> в `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). +> Пороги правил не снижались, `suppressions.xml` пустая. Реальных прогонов на сервере после правок не было (только `check`). --- diff --git a/src/main/java/dev/loki/loparkour/adaptive/bootstrap/AdaptiveServices.java b/src/main/java/dev/loki/loparkour/adaptive/bootstrap/AdaptiveServices.java index d4be7b9..a57ddcd 100644 --- a/src/main/java/dev/loki/loparkour/adaptive/bootstrap/AdaptiveServices.java +++ b/src/main/java/dev/loki/loparkour/adaptive/bootstrap/AdaptiveServices.java @@ -7,7 +7,7 @@ import dev.loki.loparkour.adaptive.model.SkillRating; import dev.loki.loparkour.adaptive.storage.FileStatsStorage; import dev.loki.loparkour.adaptive.storage.SQLStatsStorage; import dev.loki.loparkour.adaptive.storage.StatsRepository; -import dev.loki.loparkour.config.options.Option; +import dev.loki.loparkour.config.options.section.OptionSQL; import dev.loki.loparkour.generator.core.coordinator.ParkourGenerator; import dev.loki.loparkour.generator.core.model.Profile; import dev.loki.loparkour.player.core.ParkourPlayer; @@ -42,7 +42,7 @@ public final class AdaptiveServices { } final Runnable setup = () -> startCollector(createRepository()); - if (Option.SQL) { + if (OptionSQL.enabled()) { Storage.runWhenReady(setup); } else { setup.run(); @@ -54,7 +54,7 @@ public final class AdaptiveServices { AdaptiveConfig.reload(); if (plugin != null && AdaptiveConfig.isEnabled()) { final Runnable setup = () -> startCollector(createRepository()); - if (Option.SQL) { + if (OptionSQL.enabled()) { Storage.runWhenReady(setup); } else { setup.run(); @@ -147,7 +147,7 @@ public final class AdaptiveServices { @NotNull private static StatsRepository createRepository() { - if (Option.SQL && StorageSQL.isConnected()) { + if (OptionSQL.enabled() && StorageSQL.isConnected()) { return new SQLStatsStorage(StorageSQL.getConnectionManager()); } return new FileStatsStorage(); diff --git a/src/main/java/dev/loki/loparkour/bootstrap/PluginBootstrap.java b/src/main/java/dev/loki/loparkour/bootstrap/PluginBootstrap.java index 5fe6ab2..a1d3e5c 100644 --- a/src/main/java/dev/loki/loparkour/bootstrap/PluginBootstrap.java +++ b/src/main/java/dev/loki/loparkour/bootstrap/PluginBootstrap.java @@ -6,7 +6,7 @@ import dev.loki.loparkour.api.core.Registry; import dev.loki.loparkour.command.core.LoParkourCommand; import dev.loki.loparkour.config.core.Config; import dev.loki.loparkour.config.locale.Locales; -import dev.loki.loparkour.config.options.Option; +import dev.loki.loparkour.config.options.section.OptionSQL; import dev.loki.loparkour.duels.DuelsMode; import dev.loki.loparkour.ghost.service.GhostService; import dev.loki.loparkour.hook.holo.HoloHook; @@ -141,7 +141,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(Option.SQL))); + 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("locale_count", () -> Integer.toString(Locales.getLocaleCount()))); metrics.addCustomChart(new SingleLineChart("player_joins", () -> diff --git a/src/main/java/dev/loki/loparkour/command/schematic/wand/SchematicWand.java b/src/main/java/dev/loki/loparkour/command/schematic/wand/SchematicWand.java index 4133b37..1169514 100644 --- a/src/main/java/dev/loki/loparkour/command/schematic/wand/SchematicWand.java +++ b/src/main/java/dev/loki/loparkour/command/schematic/wand/SchematicWand.java @@ -2,7 +2,7 @@ package dev.loki.loparkour.command.schematic.wand; import dev.loki.loparkour.command.schematic.SchematicCommandHandler; import dev.loki.loparkour.config.locale.Locales; -import dev.loki.loparkour.config.options.Option; +import dev.loki.loparkour.config.options.OptionGeneral; import dev.loki.loparkour.menu.core.screen.ParkourOption; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -41,7 +41,7 @@ public final class SchematicWand { return cachedWand; } - String locale = Option.OPTIONS_DEFAULTS.get(ParkourOption.LANG); + String locale = OptionGeneral.optionsDefaults().get(ParkourOption.LANG); if (locale == null || locale.isBlank()) { locale = "en"; } 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 e2b33db..d38fd41 100644 --- a/src/main/java/dev/loki/loparkour/config/core/ConfigLoader.java +++ b/src/main/java/dev/loki/loparkour/config/core/ConfigLoader.java @@ -79,14 +79,36 @@ public final class ConfigLoader { } } - if (Config.GENERATION.isPath("generation.normal.height")) { - final double sum = Config.GENERATION.getDouble("generation.normal.height.1") - + Config.GENERATION.getDouble("generation.normal.height.0") - + Config.GENERATION.getDouble("generation.normal.height.-1") - + Config.GENERATION.getDouble("generation.normal.height.-2"); - if (sum <= 0) { - log.severe("Invalid config: generation height chances sum to %.1f, must be > 0".formatted(sum)); + if (Config.GENERATION.isPath("advanced.height.min") && Config.GENERATION.isPath("advanced.height.max")) { + final int heightMax = Config.GENERATION.getInt("advanced.height.max"); + final int heightMin = Config.GENERATION.getInt("advanced.height.min"); + if (heightMax < heightMin) { + log.severe("Invalid config: advanced.height.max (%d) must be >= advanced.height.min (%d)" + .formatted(heightMax, heightMin)); } } + + validateWeights(log, "advanced.distance.chances"); + validateWeights(log, "advanced.height.chances"); + validateWeights(log, "advanced.type"); + } + + /** + * Block/jump weights are divided by their sum, so a section that adds up to + * zero silently produces no blocks at all. + */ + private static void validateWeights(@NotNull Logger log, @NotNull String section) { + if (!Config.GENERATION.isPath(section)) { + return; + } + + double sum = 0.0; + for (final String key : Config.GENERATION.getChildren(section)) { + sum += Config.GENERATION.getDouble(section + "." + key); + } + + if (sum <= 0.0) { + log.severe("Invalid config: %s weights sum to %.1f, must be > 0".formatted(section, sum)); + } } } 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 f77f6a3..816f0ee 100644 --- a/src/main/java/dev/loki/loparkour/config/locale/Locales.java +++ b/src/main/java/dev/loki/loparkour/config/locale/Locales.java @@ -1,7 +1,7 @@ package dev.loki.loparkour.config.locale; import dev.loki.loparkour.LoParkour; -import dev.loki.loparkour.config.options.Option; +import dev.loki.loparkour.config.options.OptionGeneral; import dev.loki.loparkour.menu.core.screen.ParkourOption; import dev.loki.loparkour.player.core.ParkourUser; import dev.loki.loparkour.util.text.ColorUtil; @@ -56,8 +56,8 @@ public final class Locales { if (sender instanceof Player player) { return getString(player, path); } - String locale = Option.OPTIONS_DEFAULTS != null - ? Option.OPTIONS_DEFAULTS.get(ParkourOption.LANG) + String locale = OptionGeneral.optionsDefaults() != null + ? OptionGeneral.optionsDefaults().get(ParkourOption.LANG) : null; if (locale == null || locale.isBlank()) { locale = "en"; @@ -170,11 +170,11 @@ public final class Locales { @NotNull private static String getPlayerLocale(@NotNull Player player) { final ParkourUser user = ParkourUser.getUser(player); - String loc = user == null ? Option.OPTIONS_DEFAULTS.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 = Option.OPTIONS_DEFAULTS.get(ParkourOption.LANG); + loc = OptionGeneral.optionsDefaults().get(ParkourOption.LANG); if (user != null) { user.locale = 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 3f9e01d..be9ff02 100644 --- a/src/main/java/dev/loki/loparkour/config/options/Option.java +++ b/src/main/java/dev/loki/loparkour/config/options/Option.java @@ -2,37 +2,25 @@ package dev.loki.loparkour.config.options; import dev.loki.loparkour.api.core.Registry; import dev.loki.loparkour.config.core.Config; -import dev.loki.loparkour.menu.core.screen.ParkourOption; -import dev.loki.loparkour.style.core.RandomStyle; -import dev.loki.loparkour.style.core.Style; -import dev.loki.loparkour.util.particle.ParticleData; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Particle; -import org.bukkit.Sound; -import org.bukkit.block.BlockFace; -import org.bukkit.configuration.file.FileConfiguration; - -import java.util.List; -import java.util.Map; -import java.util.Set; import dev.loki.loparkour.config.options.section.OptionGeneration; import dev.loki.loparkour.config.options.section.OptionParticles; import dev.loki.loparkour.config.options.section.OptionSQL; import dev.loki.loparkour.config.options.section.OptionStyles; -import java.util.function.BiFunction; +import dev.loki.loparkour.style.core.RandomStyle; /** - * Facade for all config-derived constants. - * Delegates to specialized option classes in config.options package. + * Reload entry point for the option sections. + * + *

Each section owns its values and exposes them through accessors + * ({@link OptionSQL}, {@link OptionParticles}, {@link OptionGeneration}, + * {@link OptionGeneral}); this class does not mirror them.

*/ public final class Option { private Option() { } - // ── Initialization ──────────────────────────────────────────────────────── - + /** (Re)read every option section and re-register the configured styles. */ public static void init(boolean firstLoad) { OptionSQL.init(); OptionParticles.init(); @@ -40,129 +28,7 @@ public final class Option { OptionGeneral.init(firstLoad); OptionGeneral.initOptions(); - // Sync all fields to Option facade - syncFields(); - OptionStyles.initStyles("styles.list", Config.CONFIG.fileConfiguration, RandomStyle::new) .forEach(Registry::register); } - - // ── Direct field re-exports for backward compatibility ─────────────────── - - // SQL - public static boolean SQL; - public static String SQL_URL; - public static int SQL_PORT; - public static String SQL_DB; - public static String SQL_USERNAME; - public static String SQL_PASSWORD; - public static String SQL_PREFIX; - - // Particles - public static ParticleShape PARTICLE_SHAPE; - public static Sound SOUND_TYPE; - public static int SOUND_PITCH; - public static int SOUND_VOLUME; - public static Particle PARTICLE_TYPE; - public static ParticleData PARTICLE_DATA; - - // Generation - public static double TYPE_NORMAL; - public static double TYPE_SPECIAL; - public static double TYPE_SCHEMATICS; - public static double SPECIAL_ICE; - public static double SPECIAL_SLAB; - public static double SPECIAL_PANE; - public static double SPECIAL_FENCE; - public static double NORMAL_DISTANCE_1; - public static double NORMAL_DISTANCE_2; - public static double NORMAL_DISTANCE_3; - public static double NORMAL_DISTANCE_4; - public static double NORMAL_HEIGHT_1; - public static double NORMAL_HEIGHT_0; - public static double NORMAL_HEIGHT_NEG1; - public static double NORMAL_HEIGHT_NEG2; - public static int MAX_Y; - public static int MIN_Y; - public static int BLOCK_CLEANUP_DISTANCE; - public static int CLEANUP_INTERVAL; - public static int TRAIL_KEEP_BEHIND; - public static boolean JUMP_VALIDATION_ENABLED; - public static double MAX_JUMP_DISTANCE; - public static double MAX_HORIZONTAL_DISTANCE; - public static double MAX_VERTICAL_UP; - public static double MAX_VERTICAL_DOWN; - - // General - public static double BORDER_SIZE; - public static List POSSIBLE_LEADS; - public static BlockFace HEADING; - public static Location GO_BACK_LOC; - public static Map OPTIONS_ENABLED; - public static Map OPTIONS_DEFAULTS; - - private static void syncFields() { - // SQL - SQL = OptionSQL.SQL; - SQL_URL = OptionSQL.SQL_URL; - SQL_PORT = OptionSQL.SQL_PORT; - SQL_DB = OptionSQL.SQL_DB; - SQL_USERNAME = OptionSQL.SQL_USERNAME; - SQL_PASSWORD = OptionSQL.SQL_PASSWORD; - SQL_PREFIX = OptionSQL.SQL_PREFIX; - - // Particles - PARTICLE_SHAPE = OptionParticles.PARTICLE_SHAPE; - SOUND_TYPE = OptionParticles.SOUND_TYPE; - SOUND_PITCH = OptionParticles.SOUND_PITCH; - SOUND_VOLUME = OptionParticles.SOUND_VOLUME; - PARTICLE_TYPE = OptionParticles.PARTICLE_TYPE; - PARTICLE_DATA = OptionParticles.PARTICLE_DATA; - - // Generation - TYPE_NORMAL = OptionGeneration.TYPE_NORMAL; - TYPE_SPECIAL = OptionGeneration.TYPE_SPECIAL; - TYPE_SCHEMATICS = OptionGeneration.TYPE_SCHEMATICS; - SPECIAL_ICE = OptionGeneration.SPECIAL_ICE; - SPECIAL_SLAB = OptionGeneration.SPECIAL_SLAB; - SPECIAL_PANE = OptionGeneration.SPECIAL_PANE; - SPECIAL_FENCE = OptionGeneration.SPECIAL_FENCE; - NORMAL_DISTANCE_1 = OptionGeneration.NORMAL_DISTANCE_1; - NORMAL_DISTANCE_2 = OptionGeneration.NORMAL_DISTANCE_2; - NORMAL_DISTANCE_3 = OptionGeneration.NORMAL_DISTANCE_3; - NORMAL_DISTANCE_4 = OptionGeneration.NORMAL_DISTANCE_4; - NORMAL_HEIGHT_1 = OptionGeneration.NORMAL_HEIGHT_1; - NORMAL_HEIGHT_0 = OptionGeneration.NORMAL_HEIGHT_0; - NORMAL_HEIGHT_NEG1 = OptionGeneration.NORMAL_HEIGHT_NEG1; - NORMAL_HEIGHT_NEG2 = OptionGeneration.NORMAL_HEIGHT_NEG2; - MAX_Y = OptionGeneration.MAX_Y; - MIN_Y = OptionGeneration.MIN_Y; - BLOCK_CLEANUP_DISTANCE = OptionGeneration.BLOCK_CLEANUP_DISTANCE; - CLEANUP_INTERVAL = OptionGeneration.CLEANUP_INTERVAL; - TRAIL_KEEP_BEHIND = OptionGeneration.TRAIL_KEEP_BEHIND; - JUMP_VALIDATION_ENABLED = OptionGeneration.JUMP_VALIDATION_ENABLED; - MAX_JUMP_DISTANCE = OptionGeneration.MAX_JUMP_DISTANCE; - MAX_HORIZONTAL_DISTANCE = OptionGeneration.MAX_HORIZONTAL_DISTANCE; - MAX_VERTICAL_UP = OptionGeneration.MAX_VERTICAL_UP; - MAX_VERTICAL_DOWN = OptionGeneration.MAX_VERTICAL_DOWN; - - // General - BORDER_SIZE = OptionGeneral.BORDER_SIZE; - POSSIBLE_LEADS = OptionGeneral.POSSIBLE_LEADS; - HEADING = OptionGeneral.HEADING; - GO_BACK_LOC = OptionGeneral.GO_BACK_LOC; - OPTIONS_ENABLED = OptionGeneral.OPTIONS_ENABLED; - OPTIONS_DEFAULTS = OptionGeneral.OPTIONS_DEFAULTS; - } - - /** @deprecated Use {@link OptionStyles#initStyles(String, FileConfiguration, BiFunction)} */ - @Deprecated - public static Set