Update dependencies and improve configuration management

- Updated LoLib dependency to version 3.0.0 in build.gradle.kts
- Added HikariCP and MySQL connector dependencies for improved database handling
- Enhanced jar minimization process to exclude JDBC pool and driver
- Refactored configuration management by introducing ConfigAccessor and ConfigLoader for better file handling and validation
- Removed deprecated GenerationOptions class and integrated its functionality into the new structure
- Improved command error handling and added cooldown hints for admin commands
This commit is contained in:
loki 2026-04-01 16:21:09 +02:00
parent 978f739045
commit 3100c53229
86 changed files with 4324 additions and 3248 deletions

View file

@ -1,9 +1,5 @@
package dev.loki.loparkour.player;
import java.util.ArrayList;
import dev.lolib.scheduler.Scheduler;
import com.google.gson.annotations.Expose;
import dev.loki.loparkour.LoParkour;
import dev.loki.loparkour.config.Config;
@ -17,10 +13,10 @@ import dev.loki.loparkour.player.data.PreviousData;
import dev.loki.loparkour.session.Session;
import dev.loki.loparkour.storage.Storage;
import dev.loki.loparkour.world.Divider;
import dev.lolib.scheduler.Scheduler;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.potion.PotionEffect;
import org.jetbrains.annotations.NotNull;
@ -38,6 +34,9 @@ public class ParkourPlayer extends ParkourUser {
public static final Map<String, PlayerSettingsManager.OptionContainer> PLAYER_COLUMNS = PlayerSettingsManager.getColumnMappings();
// Track scored blocks to prevent duplicate scoring
private final Set<Block> scoredBlocks = new HashSet<>();
public @Expose Double schematicDifficulty;
public @Expose Integer blockLead;
public @Expose Boolean particles;
@ -68,10 +67,6 @@ public class ParkourPlayer extends ParkourUser {
}
}
private static boolean parseBoolean(String string) {
return string == null || string.equals("1") || string.equals("true");
}
/**
* @param player The player.
* @return True when this player is a {@link ParkourPlayer}, false if not.
@ -111,10 +106,27 @@ public class ParkourPlayer extends ParkourUser {
}
session.removePlayers(this);
// Clear scored blocks to prevent memory leak
scoredBlocks.clear();
save(LoParkour.getPlugin().isEnabled());
}
/**
* Check if player has already scored on this block.
*/
public boolean hasScored(Block block) {
return scoredBlocks.contains(block);
}
/**
* Mark block as scored to prevent duplicate scoring.
*/
public void markScored(Block block) {
scoredBlocks.add(block);
}
/**
* Sets the user's settings. If an item is not included, the setting gets reset.
*
@ -140,7 +152,8 @@ public class ParkourPlayer extends ParkourUser {
.set("selectedTime", selectedTime.toString())
.set("style", style);
generator.overrideProfile();
// Use profileManager instead of calling overrideProfile directly on generator
generator.profileManager.overrideProfile();
}
/**

View file

@ -1,7 +1,5 @@
package dev.loki.loparkour.player;
import dev.lolib.scheduler.Scheduler;
import dev.loki.loparkour.LoParkour;
import dev.loki.loparkour.api.event.ParkourSpectateEvent;
import dev.loki.loparkour.config.Locales;
@ -9,11 +7,11 @@ import dev.loki.loparkour.player.data.PreviousData;
import dev.loki.loparkour.session.Session;
import dev.loki.loparkour.util.ColorUtil;
import dev.lolib.scheduler.Scheduler;
import dev.lolib.scheduler.ScheduledTask;
import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.GameMode;
import org.bukkit.entity.Player;
import dev.lolib.scheduler.ScheduledTask;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -26,7 +24,7 @@ import java.util.Comparator;
*/
public class ParkourSpectator extends ParkourUser {
private final dev.lolib.scheduler.ScheduledTask closestChecker;
private final ScheduledTask closestChecker;
/**
* The closest player.
*/
@ -54,7 +52,8 @@ public class ParkourSpectator extends ParkourUser {
}
}, 1);
closestChecker = Scheduler.get(LoParkour.getPlugin()).runTimerAsync(() -> {
// Use runTimer (sync) instead of runTimerAsync to safely access Bukkit API
closestChecker = Scheduler.get(LoParkour.getPlugin()).runTimer(() -> {
if (session.getPlayers().isEmpty()) {
return;
}
@ -68,6 +67,7 @@ public class ParkourSpectator extends ParkourUser {
/**
* Updates the spectator's action bar, scoreboard and checks distance.
*/
@SuppressWarnings("deprecation") // Spigot Chat API: action bar via TextComponent
public void update() {
player.spigot().sendMessage(ChatMessageType.ACTION_BAR, TextComponent.fromLegacyText(ColorUtil.color(Locales.getString(player, "play.spectator.action_bar"))));
player.setGameMode(GameMode.SPECTATOR);

View file

@ -68,6 +68,7 @@ public abstract class ParkourUser {
public final Instant joined;
private ScoreboardManager scoreboardManager;
@SuppressWarnings("deprecation") // Scoreboard: legacy registerNewObjective(String,String,String)
public ParkourUser(@NotNull Player player, @NotNull Session session, @Nullable PreviousData previousData) {
this.player = player;
this.session = session;

View file

@ -25,18 +25,21 @@ public 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 = Integer.parseInt(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 = Integer.parseInt(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) -> {
p._locale = v;
p.locale = v;
}));
COLUMN_MAPPINGS.put("schematicDifficulty", new OptionContainer(ParkourOption.SCHEMATICS, (p, v) -> p.schematicDifficulty = Double.parseDouble(v)));
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)));
}
@ -64,7 +67,49 @@ public class PlayerSettingsManager {
}
private static boolean parseBoolean(String string) {
return string == null || string.equals("1") || string.equals("true");
return string == null || string.equals("1") || string.equalsIgnoreCase("true");
}
private static int defaultBlockLead() {
return parseIntSafe(Option.OPTIONS_DEFAULTS.getOrDefault(ParkourOption.LEADS, "1"), 1);
}
private static int defaultSelectedTime() {
return parseIntSafe(Option.OPTIONS_DEFAULTS.getOrDefault(ParkourOption.TIME, "6000"), 6000);
}
private static double defaultSchematicDifficulty() {
return parseDoubleSafe(Option.OPTIONS_DEFAULTS.getOrDefault(ParkourOption.SCHEMATICS, "0.6"), 0.6);
}
private static int parseIntSafe(String v, int fallback) {
if (v == null) {
return fallback;
}
String s = v.trim();
if (s.isEmpty() || "true".equalsIgnoreCase(s) || "false".equalsIgnoreCase(s)) {
return fallback;
}
try {
return Integer.parseInt(s);
} catch (NumberFormatException e) {
return fallback;
}
}
private static double parseDoubleSafe(String v, double fallback) {
if (v == null) {
return fallback;
}
String s = v.trim();
if (s.isEmpty() || "true".equalsIgnoreCase(s) || "false".equalsIgnoreCase(s)) {
return fallback;
}
try {
return Double.parseDouble(s);
} catch (NumberFormatException e) {
return fallback;
}
}
private static void applyCollectedRewards(ParkourPlayer player, String value) {

View file

@ -68,6 +68,7 @@ class ScoreboardManager {
return new Score("?", "?", "?", 0);
}
@SuppressWarnings("deprecation") // Scoreboard: legacy registerNewObjective(String,String,String)
private void updateTitle(Score top, Score high, ParkourGenerator generator) {
Objective obj = board.getObjective("lp_sidebar");
if (obj == null) {

View file

@ -1,16 +1,14 @@
package dev.loki.loparkour.player.data;
import dev.lolib.scheduler.Scheduler;
import dev.loki.loparkour.LoParkour;
import dev.loki.loparkour.config.Config;
import dev.lolib.scheduler.Scheduler;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.io.BukkitObjectInputStream;
import org.bukkit.util.io.BukkitObjectOutputStream;
import org.jetbrains.annotations.Nullable;
import java.io.*;
@ -18,6 +16,10 @@ import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
/**
* Saved hotbar/inventory slots for restore on leave. New files use YAML
* (serializable ItemStacks); legacy Java-serialized files are still read once.
*/
public class InventoryData {
private final File file;
@ -51,11 +53,46 @@ public class InventoryData {
Scheduler.get(LoParkour.getPlugin()).runAsync(() -> loadFile(onFinish));
}
@SuppressWarnings("unchecked")
private void loadFile(Consumer<@Nullable InventoryData> onFinish) {
try {
YamlConfiguration yaml = YamlConfiguration.loadConfiguration(file);
if (!yaml.getKeys(false).isEmpty()) {
loadFromYaml(yaml);
onFinish.accept(this);
return;
}
} catch (Exception ex) {
LoParkour.getPlugin().getLogger().warning(
"YAML inventory parse failed for %s, trying legacy format: %s".formatted(file.getName(), ex.getMessage()));
}
if (file.length() == 0) {
onFinish.accept(null);
return;
}
loadLegacy(onFinish);
}
private void loadFromYaml(YamlConfiguration yaml) {
items = new HashMap<>();
for (String key : yaml.getKeys(false)) {
try {
int slot = Integer.parseInt(key);
ItemStack stack = yaml.getItemStack(key);
if (stack != null) {
items.put(slot, stack);
}
} catch (NumberFormatException ignored) {
// skip non-slot keys
}
}
}
@SuppressWarnings("unchecked")
private void loadLegacy(Consumer<@Nullable InventoryData> onFinish) {
try (BukkitObjectInputStream stream = new BukkitObjectInputStream(new BufferedInputStream(new FileInputStream(file)))) {
items = (Map<Integer, ItemStack>) stream.readObject();
onFinish.accept(this);
} catch (IOException | ClassNotFoundException ex) {
LoParkour.getPlugin().getLogger().severe("Error while reading inventory of %s from file %s".formatted(player.getName(), file.getName()) + " - " + ex.getMessage());
@ -98,9 +135,13 @@ public class InventoryData {
LoParkour.getPlugin().getLogger().severe("Error while creating file to save inventory of %s to file %s".formatted(player.getName(), file.getName()) + " - " + ex.getMessage());
}
try (ObjectOutputStream stream = new BukkitObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) {
stream.writeObject(items);
stream.flush();
try {
YamlConfiguration yaml = new YamlConfiguration();
yaml.options().header(" LoParkour inventory (YAML). Legacy binary saves are migrated on next save.");
for (Map.Entry<Integer, ItemStack> e : items.entrySet()) {
yaml.set(String.valueOf(e.getKey()), e.getValue());
}
yaml.save(file);
} catch (IOException ex) {
LoParkour.getPlugin().getLogger().severe("Error while saving inventory of %s to file %s".formatted(player.getName(), file.getName()) + " - " + ex.getMessage());
}