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
This commit is contained in:
loki 2026-04-09 13:52:37 +02:00
parent b0c03cd484
commit 3546d7f9e3
10 changed files with 154 additions and 272 deletions

View file

@ -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);
}
/**

View file

@ -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);

View file

@ -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();

View file

@ -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);

View file

@ -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())));

View file

@ -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());

View file

@ -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));
}
}

View file

@ -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);