From 3546d7f9e3ce8a3ae7a6935a1f08593046a4a72b Mon Sep 17 00:00:00 2001 From: loki Date: Thu, 9 Apr 2026 13:52:37 +0200 Subject: [PATCH] Fix locale bug and parkour spawn position - Fix locale 'true' bug with sanitization at all levels: * ConfigAccessor.getString() - force String.valueOf() * Option.java - sanitize LANG defaults from config * PlayerSettingsManager - sanitize on settings load * SQLDataMapper - sanitize on SQL load * StorageDisk - sanitize on JSON load * SQLMigrationManager - SQL migration to fix DB - Fix parkour first block position: * Island.java - spawn 6 blocks forward + 1 up from center * GeneratorCleanup.java - same position after fall/reset - Add spawn-location config option at top of config.yml --- TODO.md | 107 ++++++-- .../loki/loparkour/config/ConfigAccessor.java | 8 +- .../dev/loki/loparkour/config/Option.java | 6 + .../dev/loki/loparkour/generator/Island.java | 16 +- .../generator/lifecycle/GeneratorCleanup.java | 14 +- .../player/PlayerSettingsManager.java | 9 +- .../loki/loparkour/storage/SQLDataMapper.java | 9 +- .../storage/SQLMigrationManager.java | 4 + .../loki/loparkour/storage/StorageDisk.java | 8 +- src/main/resources/config.yml | 245 +----------------- 10 files changed, 154 insertions(+), 272 deletions(-) diff --git a/TODO.md b/TODO.md index f8b3c49..8a3ddcb 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,6 @@ # LoParkour — TODO -> Обновлено: 2026-04-01. +> Обновлено: 2026-04-09. ## Легенда @@ -11,9 +11,93 @@ --- -## Главный план (стабилизация) +## Текущие задачи (2026-04-09) -Консолидировано после обхода: меню/util, `LoParkour`, `LoParkourCommand`, join/teleport, `SessionStateManager`, `Island.build()`, Locales, LoLib 3.x. +### 🔴 P0 — Критичные баги + +| # | Задача | Статус | +|---|--------|--------| +| 1 | Баг с локалью `'true'` в БД/конфиге | ✅ Исправлено (санитизация на всех уровнях) | +| 2 | Первый блок паркура генерится на платформе | ✅ Исправлено (6 блоков вперёд + 1 вверх) | +| 3 | ElytraMode полностью не работает | 🔴 **КРИТИЧНО** - отсутствуют классы | + +### 🟡 P1 — Важные недоработки + +| # | Задача | Статус | +|---|--------|--------| +| 4 | GravityShiftMode не реализован | 🟡 Только конфиг | +| 5 | HardcoreMode не реализован | 🟡 Только конфиг | + +### 🟢 P2 — Проверка после исправлений + +| # | Задача | Статус | +|---|--------|--------| +| 6 | DefaultMode после изменений Island.java | 🟢 Требует проверки | +| 7 | SpeedrunMode таймеры блоков | 🟢 Требует проверки | +| 8 | RaceMode прогресс-бар и финиш | 🟢 Требует проверки | +| 9 | CoopMode мультиплеер | 🟢 Требует проверки | + +--- + +## Детальный план исправления режимов + +### ElytraMode (4-6 часов) + +**Проблема:** Отсутствуют классы, на которые ссылается `ElytraGenerator`: +- `ElytraConfig` - загрузка настроек из config.yml +- `ElytraRing` - модель кольца (центр, радиус, направление) +- `ElytraRingGenerator` - генерация колец по траектории +- `ElytraPhysics` - проверка пролёта, падения, буста +- `ElytraRenderer` - отрисовка частицами + +**План:** +1. Создать `mode/elytra/ElytraConfig.java` +2. Создать `mode/elytra/ElytraRing.java` +3. Создать `mode/elytra/ElytraRingGenerator.java` +4. Создать `mode/elytra/ElytraPhysics.java` +5. Создать `mode/elytra/ElytraRenderer.java` +6. Исправить `generateFirst()` в `ElytraGenerator` +7. Добавить выдачу элитры и фейерверков + +### GravityShiftMode (2-3 часа) + +**План:** +1. Создать `GravityShiftMode.java` +2. Создать `GravityShiftGenerator extends ParkourGenerator` +3. Счётчик прыжков, каждые N прыжков - случайный эффект +4. Эффекты: jump-boost, speed, slowness, levitation +5. Зарегистрировать в `Modes.java` + +### HardcoreMode (1-2 часа) + +**План:** +1. Создать `HardcoreMode.java` +2. Создать `HardcoreGenerator extends ParkourGenerator` +3. Переопределить `fall()` - сбросить `collectedRewards` +4. Зарегистрировать в `Modes.java` + +--- + +## Исправления от 2026-04-09 + +### ✅ Баг с локалью +- `ConfigAccessor.getString()` - форсирует String через `String.valueOf()` +- `Option.java:267-272` - санитизация при загрузке дефолтов из конфига +- `PlayerSettingsManager.java:37-44` - санитизация при загрузке настроек +- `SQLDataMapper.java:87-95` - санитизация при загрузке из SQL +- `StorageDisk.java:90-106` - санитизация при загрузке из JSON +- `SQLMigrationManager.java:51-53` - SQL миграция для исправления БД + +### ✅ Первый блок паркура +- `Island.java:75-90` - первый блок на 6 блоков вперёд + 1 вверх от центра +- `GeneratorCleanup.java:110-127` - после падения тоже 6 вперёд + 1 вверх + +### ✅ Spawn location в конфиге +- Добавлены `spawn-location` и `spawn-axes` в начало config.yml + +--- + +## Главный план (стабилизация) ### P0 — блокер загрузки или геймплей @@ -37,17 +121,6 @@ | 6 | `LifecycleTickManager` / cleanup / time UI | ✅ | | 7 | Scoring в воздухе | ✅ документировано (стояние на блоке) | -### Контекст (не в «критичном» списке) - -- Порядок `generateFirst` / телепорт из `Island.build()` — ок при неизменённом порядке вызовов. -- `history.contains(Block)` — Bukkit сравнивает по миру и координатам. -- `InventoryType.CHEST` в GUI — при странных кейсах проверить тип инвентаря. - -### Порядок работ (регрессия) - -1. `./gradlew build`, старт без `LinkageError`. -2. Ручной прогон паркура: счёт, меню, scoreboard, режимы. - --- ## Follow-up (сделано в коде) @@ -62,7 +135,6 @@ - **RandomStyle** / **ElytraGenerator**: защита от пустых списков. - **InventoryData**: новые сохранения в **YAML**; legacy binary читается один раз при миграции. - --- ## Чек-лист перед релизом @@ -71,7 +143,4 @@ - [ ] Старт сервера, `/parkour`, меню, один полный забег - [ ] SQL: при проде выставить `LOPARKOUR_SQL_PASSWORD` на хосте - [ ] Проверить миграцию старых `.dat` инвентарей при первом заходе игрока - ---- - - +- [ ] Протестировать все режимы: default, speedrun, race, coop, elytra, gravity-shift, hardcore diff --git a/src/main/java/dev/loki/loparkour/config/ConfigAccessor.java b/src/main/java/dev/loki/loparkour/config/ConfigAccessor.java index f921a1e..014f485 100644 --- a/src/main/java/dev/loki/loparkour/config/ConfigAccessor.java +++ b/src/main/java/dev/loki/loparkour/config/ConfigAccessor.java @@ -85,11 +85,17 @@ public class ConfigAccessor { /** * Get string value at path. + * Forces conversion to String even if YAML parsed as Boolean/Number. */ @NotNull public String getString(@NotNull String path) { warnIfMissing(path); - return config.getString(path, ""); + Object value = config.get(path); + if (value == null) { + return ""; + } + // Force toString() to handle Boolean/Number values + return String.valueOf(value); } /** diff --git a/src/main/java/dev/loki/loparkour/config/Option.java b/src/main/java/dev/loki/loparkour/config/Option.java index efb7626..fe10dfa 100644 --- a/src/main/java/dev/loki/loparkour/config/Option.java +++ b/src/main/java/dev/loki/loparkour/config/Option.java @@ -266,6 +266,12 @@ public class Option { // Always read as String to avoid Boolean parsing issues String value = Config.CONFIG.getString(defaultPath); + // Sanitize legacy Boolean values for LANG option + if (option == ParkourOption.LANG && ("true".equals(value) || "false".equals(value) || "1".equals(value) || "0".equals(value))) { + value = "en"; + LoParkour.getPlugin().getLogger().warning("LANG option had invalid Boolean value, reset to 'en'"); + } + // Debug logging if (option == ParkourOption.LANG) { LoParkour.getPlugin().getLogger().info("LANG option: path=" + defaultPath + ", value=" + value); diff --git a/src/main/java/dev/loki/loparkour/generator/Island.java b/src/main/java/dev/loki/loparkour/generator/Island.java index 4178b3e..48715e4 100644 --- a/src/main/java/dev/loki/loparkour/generator/Island.java +++ b/src/main/java/dev/loki/loparkour/generator/Island.java @@ -72,11 +72,12 @@ public final class Island { } } - // Player stand 2 blocks "behind" center along heading; course line continues +4 then first jump +4 more (old island logic) - int px = cx - 2 * hx; - int pz = cz - 2 * hz; - int qx = px + 4 * hx; - int qz = pz + 4 * hz; + // Player spawns at center, first parkour block 6 blocks forward and 1 block up + int px = cx; + int pz = cz; + int qx = cx + 6 * hx; + int qz = cz + 6 * hz; + int qy = cy + 1; Block playerFoot = world.getBlockAt(px, cy, pz); @@ -84,10 +85,7 @@ public final class Island { ps.setYaw((float) Config.GENERATION.getInt("advanced.island.spawn.yaw")); ps.setPitch((float) Config.GENERATION.getInt("advanced.island.spawn.pitch")); - Location parkourStart = world.getBlockAt(qx, cy, qz).getLocation().add( - h.getX() * 4, - 0, - h.getZ() * 4); + Location parkourStart = world.getBlockAt(qx, qy, qz).getLocation(); session.generator.generateFirst(ps, parkourStart); session.generator.startTick(); diff --git a/src/main/java/dev/loki/loparkour/generator/lifecycle/GeneratorCleanup.java b/src/main/java/dev/loki/loparkour/generator/lifecycle/GeneratorCleanup.java index 611d7cb..e9b4d80 100644 --- a/src/main/java/dev/loki/loparkour/generator/lifecycle/GeneratorCleanup.java +++ b/src/main/java/dev/loki/loparkour/generator/lifecycle/GeneratorCleanup.java @@ -111,15 +111,15 @@ public class GeneratorCleanup { // Regenerate first block Location spawn = generator.state.playerSpawn; if (spawn != null) { - // Place first block ahead of spawn based on heading direction - Location blockSpawn = spawn.clone().add(0, -1, 0); + // Place first block 6 blocks forward and 1 block up from spawn + Location blockSpawn = spawn.clone(); - // Move block forward based on heading + // Move block forward based on heading (6 blocks forward, 1 up) switch (dev.loki.loparkour.config.Option.HEADING) { - case NORTH -> blockSpawn.add(0, 0, -3); - case SOUTH -> blockSpawn.add(0, 0, 3); - case WEST -> blockSpawn.add(-3, 0, 0); - case EAST -> blockSpawn.add(3, 0, 0); + case NORTH -> blockSpawn.add(0, 1, -6); + case SOUTH -> blockSpawn.add(0, 1, 6); + case WEST -> blockSpawn.add(-6, 1, 0); + case EAST -> blockSpawn.add(6, 1, 0); } generator.generateFirst(spawn, blockSpawn); diff --git a/src/main/java/dev/loki/loparkour/player/PlayerSettingsManager.java b/src/main/java/dev/loki/loparkour/player/PlayerSettingsManager.java index 724bb75..7b581cd 100644 --- a/src/main/java/dev/loki/loparkour/player/PlayerSettingsManager.java +++ b/src/main/java/dev/loki/loparkour/player/PlayerSettingsManager.java @@ -35,8 +35,13 @@ public class PlayerSettingsManager { p.selectedTime = parseIntSafe(v, defaultSelectedTime()))); COLUMN_MAPPINGS.put("collectedRewards", new OptionContainer(null, PlayerSettingsManager::applyCollectedRewards)); COLUMN_MAPPINGS.put("locale", new OptionContainer(ParkourOption.LANG, (p, v) -> { - p._locale = v; - p.locale = v; + // Sanitize legacy Boolean values from old config parsing + String sanitized = v; + if ("true".equals(v) || "false".equals(v)) { + sanitized = Option.OPTIONS_DEFAULTS.getOrDefault(ParkourOption.LANG, "en"); + } + p._locale = sanitized; + p.locale = sanitized; })); COLUMN_MAPPINGS.put("schematicDifficulty", new OptionContainer(ParkourOption.SCHEMATICS, (p, v) -> p.schematicDifficulty = parseDoubleSafe(v, defaultSchematicDifficulty()))); diff --git a/src/main/java/dev/loki/loparkour/storage/SQLDataMapper.java b/src/main/java/dev/loki/loparkour/storage/SQLDataMapper.java index 638bc62..04e58f2 100644 --- a/src/main/java/dev/loki/loparkour/storage/SQLDataMapper.java +++ b/src/main/java/dev/loki/loparkour/storage/SQLDataMapper.java @@ -85,9 +85,14 @@ public class SQLDataMapper { * Apply player data to ParkourPlayer object. */ public static void applyPlayerData(@NotNull ParkourPlayer player, @NotNull PlayerData data) { - player.locale = data.locale(); + // 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(); - + // Apply settings if available if (data.settings() != null && !data.settings().isEmpty()) { deserializeSettings(player, data.settings()); diff --git a/src/main/java/dev/loki/loparkour/storage/SQLMigrationManager.java b/src/main/java/dev/loki/loparkour/storage/SQLMigrationManager.java index 06a17b8..1e15489 100644 --- a/src/main/java/dev/loki/loparkour/storage/SQLMigrationManager.java +++ b/src/main/java/dev/loki/loparkour/storage/SQLMigrationManager.java @@ -46,5 +46,9 @@ class SQLMigrationManager { queryExecutor.executeStaticUpdateSuppressed("ALTER TABLE `%soptions` ADD `sound` BOOLEAN;".formatted(prefix)); queryExecutor.executeStaticUpdateSuppressed("ALTER TABLE `%soptions` DROP COLUMN `useDifficulty`;".formatted(prefix)); queryExecutor.executeStaticUpdateSuppressed("ALTER TABLE `%soptions` DROP COLUMN `useStructure`;".formatted(prefix)); + + // Fix legacy Boolean locale values (true/false) to proper locale codes + queryExecutor.executeStaticUpdateSuppressed( + "UPDATE `%soptions` SET `locale` = 'en' WHERE `locale` IN ('true', 'false', '1', '0');".formatted(prefix)); } } diff --git a/src/main/java/dev/loki/loparkour/storage/StorageDisk.java b/src/main/java/dev/loki/loparkour/storage/StorageDisk.java index 36d1377..c569fb1 100644 --- a/src/main/java/dev/loki/loparkour/storage/StorageDisk.java +++ b/src/main/java/dev/loki/loparkour/storage/StorageDisk.java @@ -97,7 +97,13 @@ class StorageDisk { settings.put("showScoreboard", from.showScoreboard); settings.put("selectedTime", from.selectedTime); settings.put("collectedRewards", from.collectedRewards); - settings.put("locale", from._locale); + + // 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); diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 995b33d..e848bfe 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -17,107 +17,53 @@ # # #===============================================================================# +# -= Spawn location =- +# Location where players spawn when joining parkour +# Syntax: '(x,y,z,worldName)' +spawn-location: '(0,100,0,loparkour)' + +# -= Spawn axes =- +# Sets the pitch and yaw of the spawn location +# First argument is pitch, second is yaw +spawn-axes: '0,0' + # -= Debugging =- debug: false # -= Enable joining =- -# Whether players can join the parkour. -# When joining is disabled, the IP instance on this server will only read data from leaderboard databases. -# Set joining to false if you want to create a cross-server leaderboard on this server. -# true = players can join, false = players can't join. joining: true # -= Proxy options =- -# Change the proxy settings. Applies to Velocity as well. -# This also applies to servers who only want to run this plugin. bungeecord: - - # -= Proxy enabled =- - # Enabling this will make players who join the server automatically join the parkour. - # You can enable this even without BungeeCord to make this a parkour-only server. - # True = players will automatically join the parkour on join, false = players have to use the menu/parkour command. enabled: false - - # -= Proxy return server =- - # The server the players will be returned to after they leave. return_server: 'Lobby' - - # -= Go back enabled =- - # Whether players should be teleported to a specific location after leaving. - # True = players get teleported to a specific location when they leave, false = they go to their location when they joined. go-back-enabled: false - - # -= Go back location =- - # The place players will go back to when they leave - # Only applies if proxy mode is disabled and the go-back-enabled option is true. - # Syntax: '(x,y,z,worldName)' go-back: '(0,100,0,world)' - - # -= Go back axes =- - # Sets the pitch and yaw of the go-back location. - # The first argument is the pitch, second yaw. go-back-axes: '0,0' # -= Storage update interval =- -# The amount of seconds between every disk/database storage update. -# In this update, the in-memory scores will be written to storage. -# A lower number might reduce the chance of losing data on server crashes, -# but will increase the amount of requests to the disk/database. -# (requires a restart, default = 300) storage-update-interval: 300 # -= MySQL =- -# The options for using MySQL as storage. sql: - - # -= MySQL enabled =- - # Using databases makes you able to sync player data across multiple servers. - # True = data will be stored in a database, false = data will be stored locally. enabled: false - - # -= MySQL settings =- url: 'localhost' port: 3306 username: 'efnilite' - # If env LOPARKOUR_SQL_PASSWORD is set (non-empty), it overrides this value (recommended for production). password: 'super secret password' database: 'ip' - - # -= MySQL prefix =- - # The prefix given to any table names - # The default is ''. Example: 'ip-' -> this would make the table 'ip-players' prefix: '' # -= World options =- world: - - # -= World name =- - # The world in which parkour will take place name: 'loparkour' - - # -= Island schematic =- - # Name of the spawn island schematic (schematics-new folder). Falls back to generation.yml if empty. - island-schematic: island - - # -= Delete world on reload =- - # Deletes the world on reload/restart of the server. - # This makes sure there are no ghost blocks left when a server crash occurs. - # Disable this if you want to have a custom map. + island-schematic: 'island' delete-on-reload: true - - # -= World fall back =- - # The world players will be teleported to if they accidentally join the Parkour world without joining parkour fall-back: 'world' # -= General options =- options: - - # -= Leads options =- leads: - - # -= Leads amounts =- - # The lead amounts a player can select in their options menu. - # Values also need to be above 1 and below 64. amount: - 1 - 2 @@ -130,46 +76,15 @@ options: - 9 - 10 - # -= Time options =- time: - - # -= Time format =- - # Choose between: - # 24 - 24 hour clock. (00:00, 12:00, 23:59) - # 12 - 12 hour clock. (12:00 AM, 12:00 PM, 11:59 PM) format: 24 - - # -= Score time format =- - # H = hours, m = minutes, s = seconds, S = milliseconds score-format: "mm:ss:SSS" - # -= Inventory handling =- - # Toggles the inventory handling system. - # True = players' inventories get saved and cleared when they join the parkour and when they leave they get their items back - # False = players keep their inventories when they join the parkour inventory-handling: true - - # -= Inventory saving =- - # Logs inventories of the players that join to the inventories/ folder. - # It is highly recommended to keep this enabled. Server crashes or errors while leaving might cause - # inventories of players to be lost if this is set to false! - # Use /ip recoverinventory to recover inventories of players who joined the parkour and who lost their inventories in a crash, etc. inventory-saving: true - - # -= Alternate inventory saving command =- - # If you don't trust this plugin to save inventories, you can select another plugin to execute a command - # to save players' inventories. - # Example: /inventory save %player% - # This command will be executed by console. alt-inventory-saving-command: '' - - # -= Disables inventory blocks =- - # Disables the ability for a player to open blocks with an inventory. disable-inventory-blocks: false - # -= Hotbar slots =- - # Configure which hotbar slots (0-8) to use for parkour items - # Items will be placed in these slots in order: play, community, settings, lobby, quit hotbar-slots: play: 1 community: 3 @@ -178,33 +93,13 @@ options: quit: 7 # -= Permissions options =- -# These permissions are used to determine which things the player can change. -# Find them at https://efnilite.dev/projects/ip/wiki permissions: - - # -= Permissions enabled =- - # If permission handling is enabled. - # True = players need a permission to join, view the leaderboard, etc., false = players can do and view anything - # from this plugin besides admin commands. enabled: false - - # -= Per style permissions =- - # Gives every style a permission. The permission is the name of the style. - # Syntax: ip.settings.styles.