chore: unify checkstyle, fix violations, cleanup bin/
This commit is contained in:
parent
1d8c0ca483
commit
a0c1ab8ca1
156 changed files with 5222 additions and 4348 deletions
|
|
@ -8,24 +8,18 @@ import dev.loki.lomines.data.config.loader.region.WorldGuardConfigLoader;
|
|||
import dev.loki.lomines.data.config.loader.reward.RewardConfigLoader;
|
||||
import dev.loki.lomines.data.config.loader.system.ResetConfigLoader;
|
||||
import dev.loki.lomines.data.config.loader.system.UIConfigLoader;
|
||||
import dev.loki.lomines.data.config.model.MineConfig;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Clean config loader for section-based configuration.
|
||||
* Supports YAML inheritance from defaults.
|
||||
* Delegates to specialized loaders for each section.
|
||||
*/
|
||||
public final class ConfigLoader {
|
||||
|
||||
private final Path dataFolder;
|
||||
private YamlConfiguration defaults;
|
||||
private final DefaultsMerger defaultsMerger;
|
||||
|
||||
// Section loaders
|
||||
private RegionConfigLoader regionLoader;
|
||||
private BlockConfigLoader blockLoader;
|
||||
private ResetConfigLoader resetLoader;
|
||||
|
|
@ -37,69 +31,29 @@ public final class ConfigLoader {
|
|||
|
||||
public ConfigLoader(Path dataFolder) {
|
||||
this.dataFolder = dataFolder;
|
||||
loadDefaults();
|
||||
this.defaultsMerger = new DefaultsMerger(dataFolder);
|
||||
initLoaders();
|
||||
}
|
||||
|
||||
private void loadDefaults() {
|
||||
Path defaultsPath = dataFolder.resolve("mines").resolve("_defaults.yml");
|
||||
defaults = new YamlConfiguration();
|
||||
|
||||
if (Files.exists(defaultsPath)) {
|
||||
defaults = YamlConfiguration.loadConfiguration(defaultsPath.toFile());
|
||||
} else {
|
||||
setDefaults(defaults);
|
||||
try {
|
||||
Files.createDirectories(defaultsPath.getParent());
|
||||
defaults.save(defaultsPath.toFile());
|
||||
} catch (IOException e) {
|
||||
// Ignore, use in-memory defaults
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setDefaults(YamlConfiguration yaml) {
|
||||
yaml.set("reset.interval", "5m");
|
||||
yaml.set("reset.percent-trigger", 10.0);
|
||||
yaml.set("reset.percent-enabled", false);
|
||||
yaml.set("reset.commands", List.of());
|
||||
yaml.set("reset.broadcast", "");
|
||||
|
||||
yaml.set("ui.actionbar.enabled", true);
|
||||
yaml.set("ui.actionbar.format", "<green>{mine}</green> <gray>{percent}%</gray> <dark_gray>({time})");
|
||||
yaml.set("ui.actionbar.range", 50.0);
|
||||
yaml.set("ui.timer-format", "mm:ss");
|
||||
|
||||
yaml.set("teleport.enabled", false);
|
||||
yaml.set("rewards", List.of());
|
||||
|
||||
worldGuardLoader.setDefaults(yaml);
|
||||
playerSpawnLoader.setDefaults(yaml);
|
||||
}
|
||||
|
||||
private void initLoaders() {
|
||||
regionLoader = new RegionConfigLoader();
|
||||
blockLoader = new BlockConfigLoader();
|
||||
resetLoader = new ResetConfigLoader(defaults);
|
||||
resetLoader = new ResetConfigLoader(defaultsMerger.getDefaults());
|
||||
rewardLoader = new RewardConfigLoader();
|
||||
teleportLoader = new TeleportConfigLoader();
|
||||
uiLoader = new UIConfigLoader(defaults);
|
||||
worldGuardLoader = new WorldGuardConfigLoader(defaults);
|
||||
uiLoader = new UIConfigLoader(defaultsMerger.getDefaults());
|
||||
worldGuardLoader = new WorldGuardConfigLoader(defaultsMerger.getDefaults());
|
||||
playerSpawnLoader = new PlayerSpawnConfigLoader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a mine configuration from file.
|
||||
*/
|
||||
public MineConfig load(String mineName) throws ConfigLoadException {
|
||||
Path configPath = dataFolder.resolve("mines").resolve(mineName + ".yml");
|
||||
|
||||
if (!Files.exists(configPath)) {
|
||||
if (!Files.exists(dataFolder.resolve("mines").resolve(mineName + ".yml"))) {
|
||||
throw new ConfigLoadException("Mine not found: " + mineName);
|
||||
}
|
||||
|
||||
Path configPath = dataFolder.resolve("mines").resolve(mineName + ".yml");
|
||||
YamlConfiguration yaml = YamlConfiguration.loadConfiguration(configPath.toFile());
|
||||
mergeDefaults(yaml);
|
||||
defaultsMerger.mergeDefaults(yaml);
|
||||
|
||||
try {
|
||||
return MineConfig.builder(mineName)
|
||||
|
|
@ -117,11 +71,7 @@ public final class ConfigLoader {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a mine configuration to file.
|
||||
*/
|
||||
public void save(MineConfig config) throws ConfigLoadException {
|
||||
Path configPath = dataFolder.resolve("mines").resolve(config.name() + ".yml");
|
||||
YamlConfiguration yaml = new YamlConfiguration();
|
||||
|
||||
regionLoader.save(yaml, config.region());
|
||||
|
|
@ -134,6 +84,7 @@ public final class ConfigLoader {
|
|||
playerSpawnLoader.save(yaml, config.playerSpawn());
|
||||
|
||||
try {
|
||||
Path configPath = dataFolder.resolve("mines").resolve(config.name() + ".yml");
|
||||
Files.createDirectories(configPath.getParent());
|
||||
yaml.save(configPath.toFile());
|
||||
} catch (IOException e) {
|
||||
|
|
@ -141,17 +92,6 @@ public final class ConfigLoader {
|
|||
}
|
||||
}
|
||||
|
||||
private void mergeDefaults(YamlConfiguration yaml) {
|
||||
for (String key : defaults.getKeys(true)) {
|
||||
if (!yaml.contains(key)) {
|
||||
yaml.set(key, defaults.get(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception for config loading errors.
|
||||
*/
|
||||
public static class ConfigLoadException extends Exception {
|
||||
public ConfigLoadException(String message) {
|
||||
super(message);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
package dev.loki.lomines.data.config;
|
||||
|
||||
import dev.loki.lomines.data.config.loader.region.WorldGuardConfigLoader;
|
||||
import dev.loki.lomines.data.config.loader.entity.PlayerSpawnConfigLoader;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
public final class DefaultsMerger {
|
||||
|
||||
private final Path dataFolder;
|
||||
private YamlConfiguration defaults;
|
||||
|
||||
public DefaultsMerger(Path dataFolder) {
|
||||
this.dataFolder = dataFolder;
|
||||
loadDefaults();
|
||||
}
|
||||
|
||||
public YamlConfiguration getDefaults() {
|
||||
return defaults;
|
||||
}
|
||||
|
||||
private void loadDefaults() {
|
||||
Path defaultsPath = dataFolder.resolve("mines").resolve("_defaults.yml");
|
||||
defaults = new YamlConfiguration();
|
||||
|
||||
if (Files.exists(defaultsPath)) {
|
||||
defaults = YamlConfiguration.loadConfiguration(defaultsPath.toFile());
|
||||
} else {
|
||||
setDefaults(defaults);
|
||||
try {
|
||||
Files.createDirectories(defaultsPath.getParent());
|
||||
defaults.save(defaultsPath.toFile());
|
||||
} catch (IOException e) {
|
||||
// Ignore, use in-memory defaults
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setDefaults(YamlConfiguration yaml) {
|
||||
yaml.set("reset.interval", "5m");
|
||||
yaml.set("reset.percent-trigger", 10.0);
|
||||
yaml.set("reset.percent-enabled", false);
|
||||
yaml.set("reset.commands", List.of());
|
||||
yaml.set("reset.broadcast", "");
|
||||
|
||||
yaml.set("ui.actionbar.enabled", true);
|
||||
yaml.set("ui.actionbar.format", "<green>{mine}</green> <gray>{percent}%</gray> <dark_gray>({time})");
|
||||
yaml.set("ui.actionbar.range", 50.0);
|
||||
yaml.set("ui.timer-format", "mm:ss");
|
||||
|
||||
yaml.set("teleport.enabled", false);
|
||||
yaml.set("rewards", List.of());
|
||||
|
||||
WorldGuardConfigLoader wgLoader = new WorldGuardConfigLoader(new YamlConfiguration());
|
||||
wgLoader.setDefaults(yaml);
|
||||
PlayerSpawnConfigLoader psLoader = new PlayerSpawnConfigLoader();
|
||||
psLoader.setDefaults(yaml);
|
||||
}
|
||||
|
||||
public void mergeDefaults(YamlConfiguration yaml) {
|
||||
for (String key : defaults.getKeys(true)) {
|
||||
if (!yaml.contains(key)) {
|
||||
yaml.set(key, defaults.get(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -32,7 +32,9 @@ public final class BlockConfigLoader {
|
|||
Map<BlockKey, Double> weights = new HashMap<>();
|
||||
for (String key : contents.getKeys(false)) {
|
||||
double weight = contents.getDouble(key);
|
||||
if (weight <= 0) continue;
|
||||
if (weight <= 0) {
|
||||
continue;
|
||||
}
|
||||
weights.put(BlockKey.deserialize(key), weight);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -66,7 +66,9 @@ public final class RegionConfigLoader {
|
|||
break;
|
||||
}
|
||||
}
|
||||
if (!hasMore) break;
|
||||
if (!hasMore) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package dev.loki.lomines.data.config.loader.region;
|
||||
|
||||
import dev.loki.lomines.integration.worldguard.WorldGuardConfig;
|
||||
import dev.loki.lomines.integration.worldguard.config.WorldGuardConfig;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
|
|
@ -38,9 +38,6 @@ public final class WorldGuardConfigLoader {
|
|||
return WorldGuardConfig.disabled();
|
||||
}
|
||||
|
||||
String template = yaml.getString("worldguard.region-template",
|
||||
defaults.getString("worldguard.region-template", "{mine_name}_{random_4}"));
|
||||
|
||||
List<String> owners = yaml.getStringList("worldguard.owners");
|
||||
if (owners.isEmpty()) {
|
||||
owners = defaults.getStringList("worldguard.owners");
|
||||
|
|
@ -63,9 +60,12 @@ public final class WorldGuardConfigLoader {
|
|||
boolean protect = yaml.getBoolean("worldguard.protect-on-create",
|
||||
defaults.getBoolean("worldguard.protect-on-create", true));
|
||||
|
||||
String tmpl = yaml.getString("worldguard.region-template",
|
||||
defaults.getString("worldguard.region-template", "{mine_name}_{random_4}"));
|
||||
|
||||
return WorldGuardConfig.builder()
|
||||
.enabled(true)
|
||||
.template(template)
|
||||
.template(tmpl)
|
||||
.owners(owners)
|
||||
.members(members)
|
||||
.flags(flags)
|
||||
|
|
|
|||
|
|
@ -36,9 +36,6 @@ public final class RewardConfigLoader {
|
|||
}
|
||||
|
||||
private RewardEntry parseRewardEntry(Map<?, ?> map) {
|
||||
Object chanceObj = map.get("chance");
|
||||
double chance = chanceObj instanceof Number n ? n.doubleValue() : 0.0;
|
||||
|
||||
List<BlockKey> blocks = new ArrayList<>();
|
||||
List<?> blockList = (List<?>) map.get("blocks");
|
||||
if (blockList != null) {
|
||||
|
|
@ -69,6 +66,8 @@ public final class RewardConfigLoader {
|
|||
Object preventDropsObj = map.get("prevent-drops");
|
||||
boolean preventDrops = preventDropsObj instanceof Boolean b && b;
|
||||
|
||||
Object chanceObj = map.get("chance");
|
||||
double chance = chanceObj instanceof Number n ? n.doubleValue() : 0.0;
|
||||
return new RewardEntry(blocks, chance, items, commands, preventDrops);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
package dev.loki.lomines.data.config.model;
|
||||
|
||||
import dev.loki.lomines.data.config.block.BlockConfig;
|
||||
import dev.loki.lomines.data.config.region.RegionConfig;
|
||||
import dev.loki.lomines.data.config.reset.ResetConfig;
|
||||
import dev.loki.lomines.data.config.reward.RewardConfig;
|
||||
import dev.loki.lomines.data.config.spawn.PlayerSpawnConfig;
|
||||
import dev.loki.lomines.data.config.teleport.TeleportConfig;
|
||||
import dev.loki.lomines.data.config.ui.UIConfig;
|
||||
import dev.loki.lomines.integration.worldguard.config.WorldGuardConfig;
|
||||
|
||||
import org.bukkit.Location;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public record MineConfig(
|
||||
String name,
|
||||
RegionConfig region,
|
||||
BlockConfig blocks,
|
||||
ResetConfig reset,
|
||||
RewardConfig rewards,
|
||||
TeleportConfig teleport,
|
||||
UIConfig ui,
|
||||
WorldGuardConfig worldGuard,
|
||||
PlayerSpawnConfig playerSpawn
|
||||
) {
|
||||
|
||||
public MineConfig {
|
||||
Objects.requireNonNull(name, "Mine name cannot be null");
|
||||
if (name.isBlank()) {
|
||||
throw new IllegalArgumentException("Mine name cannot be blank");
|
||||
}
|
||||
name = name.toLowerCase().trim();
|
||||
|
||||
Objects.requireNonNull(region, "Region config cannot be null");
|
||||
Objects.requireNonNull(blocks, "Block config cannot be null");
|
||||
Objects.requireNonNull(reset, "Reset config cannot be null");
|
||||
Objects.requireNonNull(rewards, "Reward config cannot be null");
|
||||
Objects.requireNonNull(teleport, "Teleport config cannot be null");
|
||||
Objects.requireNonNull(ui, "UI config cannot be null");
|
||||
if (playerSpawn == null) {
|
||||
playerSpawn = PlayerSpawnConfig.disabled();
|
||||
}
|
||||
}
|
||||
|
||||
public static MineConfigDefaults.Builder builder(String name) {
|
||||
return new MineConfigDefaults.Builder(name);
|
||||
}
|
||||
|
||||
public int volume() {
|
||||
return region.totalVolume();
|
||||
}
|
||||
|
||||
public String worldName() {
|
||||
return region.worldName();
|
||||
}
|
||||
|
||||
public Optional<Location> getSpawnForStuckPlayer() {
|
||||
return playerSpawn.enabled()
|
||||
? playerSpawn.getLocation()
|
||||
: teleport.getLocation();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package dev.loki.lomines.data.config;
|
||||
package dev.loki.lomines.data.config.model;
|
||||
|
||||
import dev.loki.lomines.data.config.block.BlockConfig;
|
||||
import dev.loki.lomines.data.config.region.RegionConfig;
|
||||
|
|
@ -7,58 +7,13 @@ import dev.loki.lomines.data.config.reward.RewardConfig;
|
|||
import dev.loki.lomines.data.config.spawn.PlayerSpawnConfig;
|
||||
import dev.loki.lomines.data.config.teleport.TeleportConfig;
|
||||
import dev.loki.lomines.data.config.ui.UIConfig;
|
||||
import dev.loki.lomines.integration.worldguard.WorldGuardConfig;
|
||||
import dev.loki.lomines.integration.worldguard.config.WorldGuardConfig;
|
||||
|
||||
import org.bukkit.Location;
|
||||
public final class MineConfigDefaults {
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Clean, type-safe, section-based mine configuration.
|
||||
* Proper separation of concerns: each section handles one aspect.
|
||||
*/
|
||||
public record MineConfig(
|
||||
String name,
|
||||
RegionConfig region,
|
||||
BlockConfig blocks,
|
||||
ResetConfig reset,
|
||||
RewardConfig rewards,
|
||||
TeleportConfig teleport,
|
||||
UIConfig ui,
|
||||
WorldGuardConfig worldGuard,
|
||||
PlayerSpawnConfig playerSpawn
|
||||
) {
|
||||
|
||||
public MineConfig {
|
||||
Objects.requireNonNull(name, "Mine name cannot be null");
|
||||
if (name.isBlank()) {
|
||||
throw new IllegalArgumentException("Mine name cannot be blank");
|
||||
}
|
||||
name = name.toLowerCase().trim();
|
||||
|
||||
Objects.requireNonNull(region, "Region config cannot be null");
|
||||
Objects.requireNonNull(blocks, "Block config cannot be null");
|
||||
Objects.requireNonNull(reset, "Reset config cannot be null");
|
||||
Objects.requireNonNull(rewards, "Reward config cannot be null");
|
||||
Objects.requireNonNull(teleport, "Teleport config cannot be null");
|
||||
Objects.requireNonNull(ui, "UI config cannot be null");
|
||||
// worldGuard and playerSpawn can be null (disabled by default)
|
||||
if (playerSpawn == null) {
|
||||
playerSpawn = PlayerSpawnConfig.disabled();
|
||||
}
|
||||
private MineConfigDefaults() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates builder for fluent construction.
|
||||
*/
|
||||
public static Builder builder(String name) {
|
||||
return new Builder(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates config with sensible defaults.
|
||||
*/
|
||||
public static MineConfig defaults(String name, RegionConfig region, BlockConfig blocks) {
|
||||
return new MineConfig(
|
||||
name,
|
||||
|
|
@ -73,32 +28,6 @@ public record MineConfig(
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Total volume of the mine (for progress calculation).
|
||||
*/
|
||||
public int volume() {
|
||||
return region.totalVolume();
|
||||
}
|
||||
|
||||
/**
|
||||
* World name where the mine is located.
|
||||
*/
|
||||
public String worldName() {
|
||||
return region.worldName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the spawn location for stuck players.
|
||||
* If playerSpawn is not set, falls back to teleport location.
|
||||
*/
|
||||
public Optional<Location> getSpawnForStuckPlayer() {
|
||||
return playerSpawn.enabled()
|
||||
? playerSpawn.getLocation()
|
||||
: teleport.getLocation();
|
||||
}
|
||||
|
||||
// --- Builder ---
|
||||
|
||||
public static class Builder {
|
||||
private final String name;
|
||||
private RegionConfig region;
|
||||
|
|
@ -110,7 +39,7 @@ public record MineConfig(
|
|||
private WorldGuardConfig worldGuard = WorldGuardConfig.disabled();
|
||||
private PlayerSpawnConfig playerSpawn = PlayerSpawnConfig.disabled();
|
||||
|
||||
private Builder(String name) {
|
||||
public Builder(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
package dev.loki.lomines.data.config.region;
|
||||
|
||||
import dev.loki.lomines.util.location.Cuboid;
|
||||
import dev.loki.lomines.util.location.geo.Cuboid;
|
||||
import org.bukkit.Location;
|
||||
|
||||
import java.util.Collections;
|
||||
|
|
|
|||
|
|
@ -3,9 +3,6 @@ package dev.loki.lomines.data.config.reset;
|
|||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Configuration for mine reset behavior.
|
||||
*/
|
||||
public record ResetConfig(
|
||||
Duration interval,
|
||||
double percentTrigger,
|
||||
|
|
@ -14,34 +11,16 @@ public record ResetConfig(
|
|||
String broadcastMessage
|
||||
) {
|
||||
|
||||
public static final Duration DEFAULT_INTERVAL = Duration.ofMinutes(5);
|
||||
public static final double DEFAULT_PERCENT_TRIGGER = 10.0;
|
||||
|
||||
public ResetConfig {
|
||||
// Normalize and validate interval
|
||||
if (interval == null || interval.isZero() || interval.isNegative()) {
|
||||
interval = DEFAULT_INTERVAL;
|
||||
}
|
||||
|
||||
// Ensure interval is reasonable (1 second to 24 hours)
|
||||
if (interval.getSeconds() < 1) {
|
||||
interval = Duration.ofSeconds(1);
|
||||
} else if (interval.getSeconds() > 86400) {
|
||||
interval = Duration.ofHours(24);
|
||||
}
|
||||
|
||||
// Validate percent trigger
|
||||
if (percentTrigger < 0 || percentTrigger > 100) {
|
||||
percentTrigger = DEFAULT_PERCENT_TRIGGER;
|
||||
}
|
||||
|
||||
interval = ResetConfigValidator.validateInterval(interval);
|
||||
percentTrigger = ResetConfigValidator.validatePercentTrigger(percentTrigger);
|
||||
commands = commands != null ? List.copyOf(commands) : List.of();
|
||||
broadcastMessage = broadcastMessage != null ? broadcastMessage : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Default config: 5 minutes, 10% trigger disabled.
|
||||
*/
|
||||
public static final Duration DEFAULT_INTERVAL = ResetConfigValidator.DEFAULT_INTERVAL;
|
||||
public static final double DEFAULT_PERCENT_TRIGGER = ResetConfigValidator.DEFAULT_PERCENT_TRIGGER;
|
||||
|
||||
public static ResetConfig defaults() {
|
||||
return new ResetConfig(
|
||||
DEFAULT_INTERVAL,
|
||||
|
|
@ -52,23 +31,14 @@ public record ResetConfig(
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for fluent construction.
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Interval in ticks (20 ticks = 1 second).
|
||||
*/
|
||||
public long intervalTicks() {
|
||||
return interval.getSeconds() * 20;
|
||||
}
|
||||
|
||||
/**
|
||||
* Human-readable interval string.
|
||||
*/
|
||||
public String intervalDisplay() {
|
||||
long seconds = interval.getSeconds();
|
||||
if (seconds < 60) {
|
||||
|
|
@ -80,9 +50,6 @@ public record ResetConfig(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether reset should trigger based on percent mined.
|
||||
*/
|
||||
public boolean isPercentTriggerEnabled() {
|
||||
return percentEnabled && percentTrigger > 0;
|
||||
}
|
||||
|
|
@ -94,38 +61,18 @@ public record ResetConfig(
|
|||
private List<String> commands = List.of();
|
||||
private String broadcastMessage = "";
|
||||
|
||||
private static Duration parseDuration(String s) {
|
||||
s = s.trim().toLowerCase();
|
||||
try {
|
||||
if (s.endsWith("s")) {
|
||||
return Duration.ofSeconds(Long.parseLong(s.substring(0, s.length() - 1)));
|
||||
} else if (s.endsWith("m")) {
|
||||
return Duration.ofMinutes(Long.parseLong(s.substring(0, s.length() - 1)));
|
||||
} else if (s.endsWith("h")) {
|
||||
return Duration.ofHours(Long.parseLong(s.substring(0, s.length() - 1)));
|
||||
} else if (s.endsWith("d")) {
|
||||
return Duration.ofDays(Long.parseLong(s.substring(0, s.length() - 1)));
|
||||
} else {
|
||||
// Assume seconds if no suffix
|
||||
return Duration.ofSeconds(Long.parseLong(s));
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
return DEFAULT_INTERVAL;
|
||||
}
|
||||
}
|
||||
|
||||
public Builder interval(Duration interval) {
|
||||
this.interval = interval;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder intervalTicks(long ticks) {
|
||||
this.interval = Duration.ofSeconds(ticks / 20);
|
||||
public Builder interval(String humanReadable) {
|
||||
this.interval = ResetConfigValidator.parseDuration(humanReadable);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder interval(String humanReadable) {
|
||||
this.interval = parseDuration(humanReadable);
|
||||
public Builder intervalTicks(long ticks) {
|
||||
this.interval = Duration.ofSeconds(ticks / 20);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
package dev.loki.lomines.data.config.reset;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
public final class ResetConfigValidator {
|
||||
|
||||
public static final Duration DEFAULT_INTERVAL = Duration.ofMinutes(5);
|
||||
public static final double DEFAULT_PERCENT_TRIGGER = 10.0;
|
||||
|
||||
private ResetConfigValidator() {
|
||||
}
|
||||
|
||||
public static Duration validateInterval(Duration interval) {
|
||||
if (interval == null || interval.isZero() || interval.isNegative()) {
|
||||
interval = DEFAULT_INTERVAL;
|
||||
}
|
||||
if (interval.getSeconds() < 1) {
|
||||
interval = Duration.ofSeconds(1);
|
||||
} else if (interval.getSeconds() > 86400) {
|
||||
interval = Duration.ofHours(24);
|
||||
}
|
||||
return interval;
|
||||
}
|
||||
|
||||
public static double validatePercentTrigger(double percentTrigger) {
|
||||
if (percentTrigger < 0 || percentTrigger > 100) {
|
||||
return DEFAULT_PERCENT_TRIGGER;
|
||||
}
|
||||
return percentTrigger;
|
||||
}
|
||||
|
||||
public static Duration parseDuration(String s) {
|
||||
s = s.trim().toLowerCase();
|
||||
try {
|
||||
if (s.endsWith("s")) {
|
||||
return Duration.ofSeconds(Long.parseLong(s.substring(0, s.length() - 1)));
|
||||
} else if (s.endsWith("m")) {
|
||||
return Duration.ofMinutes(Long.parseLong(s.substring(0, s.length() - 1)));
|
||||
} else if (s.endsWith("h")) {
|
||||
return Duration.ofHours(Long.parseLong(s.substring(0, s.length() - 1)));
|
||||
} else if (s.endsWith("d")) {
|
||||
return Duration.ofDays(Long.parseLong(s.substring(0, s.length() - 1)));
|
||||
} else {
|
||||
return Duration.ofSeconds(Long.parseLong(s));
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
return DEFAULT_INTERVAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
package dev.loki.lomines.data.config.ui;
|
||||
|
||||
import dev.loki.lomines.util.format.ColorUtils;
|
||||
import dev.loki.lomines.util.format.color.ColorUtils;
|
||||
import net.kyori.adventure.text.Component;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Configuration for UI elements (action bar, messages).
|
||||
*/
|
||||
|
|
@ -16,6 +18,7 @@ public record UIConfig(
|
|||
|
||||
public static final String DEFAULT_ACTIONBAR_FORMAT = "<green>{mine}</green> <gray>{percent}%</gray> <dark_gray>({time})";
|
||||
public static final String DEFAULT_TIMER_FORMAT = "mm:ss";
|
||||
|
||||
public UIConfig {
|
||||
hologram = hologram != null ? hologram : HologramConfig.disabled();
|
||||
actionBarFormat = actionBarFormat != null && !actionBarFormat.isBlank()
|
||||
|
|
@ -49,7 +52,7 @@ public record UIConfig(
|
|||
public Component formatActionBar(String mineName, double percent, String timeStr, int blocks, int total) {
|
||||
String parsed = actionBarFormat
|
||||
.replace("{mine}", mineName)
|
||||
.replace("{percent}", String.format("%.1f", percent))
|
||||
.replace("{percent}", String.format(Locale.US, "%.1f", percent))
|
||||
.replace("{time}", timeStr)
|
||||
.replace("{blocks}", String.valueOf(blocks))
|
||||
.replace("{total}", String.valueOf(total));
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package dev.loki.lomines.data.reward;
|
||||
package dev.loki.lomines.data.reward.entity;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
|
@ -49,8 +49,12 @@ public record Reward(List<Material> materials, double chance, List<ItemStack> it
|
|||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Reward reward = (Reward) o;
|
||||
return Double.compare(reward.chance, chance) == 0 &&
|
||||
preventDrops == reward.preventDrops &&
|
||||
|
|
@ -59,4 +63,8 @@ public record Reward(List<Material> materials, double chance, List<ItemStack> it
|
|||
Objects.equals(commands, reward.commands);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(materials, chance, items, commands, preventDrops);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,69 +1,29 @@
|
|||
package dev.loki.lomines.data.reward;
|
||||
package dev.loki.lomines.data.reward.parse;
|
||||
|
||||
import dev.loki.lomines.data.config.parser.ConfigParseException;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import dev.loki.lomines.data.reward.entity.Reward;
|
||||
import dev.loki.lomines.data.reward.parse.command.RewardCommandParser;
|
||||
import dev.loki.lomines.data.reward.parse.item.RewardItemParser;
|
||||
import dev.loki.lomines.data.reward.parse.item.RewardMaterialParser;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Parses reward configurations from YAML.
|
||||
* Handles reward chance, materials, items, commands, and prevent-drops settings.
|
||||
*/
|
||||
public final class RewardParser {
|
||||
public final class RewardEntryParser {
|
||||
|
||||
private final RewardItemParser itemParser;
|
||||
private final RewardMaterialParser materialParser;
|
||||
private final RewardCommandParser commandParser;
|
||||
|
||||
public RewardParser() {
|
||||
public RewardEntryParser() {
|
||||
this.itemParser = new RewardItemParser();
|
||||
this.materialParser = new RewardMaterialParser();
|
||||
this.commandParser = new RewardCommandParser();
|
||||
}
|
||||
|
||||
public List<Reward> parseRewards(YamlConfiguration yaml) throws ConfigParseException {
|
||||
List<Reward> rewards = new ArrayList<>();
|
||||
|
||||
if (!yaml.contains("random-rewards")) {
|
||||
return rewards;
|
||||
}
|
||||
|
||||
List<?> rewardsList = yaml.getList("random-rewards");
|
||||
if (rewardsList == null) {
|
||||
return rewards;
|
||||
}
|
||||
|
||||
for (int i = 0; i < rewardsList.size(); i++) {
|
||||
Object rewardObj = rewardsList.get(i);
|
||||
|
||||
if (!(rewardObj instanceof Map)) {
|
||||
throw new ConfigParseException(
|
||||
"Invalid reward at index " + i + ": expected map, got " +
|
||||
(rewardObj != null ? rewardObj.getClass().getSimpleName() : "null")
|
||||
);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> rewardMap = (Map<String, Object>) rewardObj;
|
||||
|
||||
try {
|
||||
Reward reward = parseReward(rewardMap);
|
||||
rewards.add(reward);
|
||||
} catch (ConfigParseException e) {
|
||||
throw new ConfigParseException(
|
||||
"Error parsing reward at index " + i + ": " + e.getMessage(),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return rewards;
|
||||
}
|
||||
|
||||
private Reward parseReward(Map<String, Object> rewardMap) throws ConfigParseException {
|
||||
public Reward parseReward(Map<String, Object> rewardMap) throws ConfigParseException {
|
||||
double chance = parseRewardField(rewardMap, "chance", Double.class);
|
||||
boolean preventDrops = parseRewardField(rewardMap, "prevent-drops", Boolean.class, false);
|
||||
|
||||
|
|
@ -75,7 +35,7 @@ public final class RewardParser {
|
|||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T parseRewardField(Map<String, Object> map, String key, Class<T> type)
|
||||
public <T> T parseRewardField(Map<String, Object> map, String key, Class<T> type)
|
||||
throws ConfigParseException {
|
||||
if (!map.containsKey(key)) {
|
||||
throw new ConfigParseException("Missing required '" + key + "' field in reward");
|
||||
|
|
@ -96,7 +56,7 @@ public final class RewardParser {
|
|||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T parseRewardField(Map<String, Object> map, String key, Class<T> type, T defaultValue) {
|
||||
public <T> T parseRewardField(Map<String, Object> map, String key, Class<T> type, T defaultValue) {
|
||||
if (!map.containsKey(key)) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
|
@ -107,7 +67,7 @@ public final class RewardParser {
|
|||
return defaultValue;
|
||||
}
|
||||
|
||||
private Double parseDoubleValue(String key, Object value) throws ConfigParseException {
|
||||
public Double parseDoubleValue(String key, Object value) throws ConfigParseException {
|
||||
try {
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).doubleValue();
|
||||
|
|
@ -124,7 +84,7 @@ public final class RewardParser {
|
|||
}
|
||||
}
|
||||
|
||||
private List<ItemStack> parseRewardItems(Map<String, Object> rewardMap)
|
||||
public List<ItemStack> parseRewardItems(Map<String, Object> rewardMap)
|
||||
throws ConfigParseException {
|
||||
List<ItemStack> items = new ArrayList<>();
|
||||
if (!rewardMap.containsKey("items")) {
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package dev.loki.lomines.data.reward.parse;
|
||||
|
||||
import dev.loki.lomines.data.config.parser.ConfigParseException;
|
||||
import dev.loki.lomines.data.reward.entity.Reward;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public final class RewardParser {
|
||||
|
||||
private final RewardEntryParser entryParser;
|
||||
|
||||
public RewardParser() {
|
||||
this.entryParser = new RewardEntryParser();
|
||||
}
|
||||
|
||||
public List<Reward> parseRewards(YamlConfiguration yaml) throws ConfigParseException {
|
||||
List<Reward> rewards = new ArrayList<>();
|
||||
|
||||
if (!yaml.contains("random-rewards")) {
|
||||
return rewards;
|
||||
}
|
||||
|
||||
List<?> rewardsList = yaml.getList("random-rewards");
|
||||
if (rewardsList == null) {
|
||||
return rewards;
|
||||
}
|
||||
|
||||
for (int i = 0; i < rewardsList.size(); i++) {
|
||||
Object rewardObj = rewardsList.get(i);
|
||||
|
||||
if (!(rewardObj instanceof Map)) {
|
||||
throw new ConfigParseException(
|
||||
"Invalid reward at index " + i + ": expected map, got " +
|
||||
(rewardObj != null ? rewardObj.getClass().getSimpleName() : "null")
|
||||
);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> rewardMap = (Map<String, Object>) rewardObj;
|
||||
|
||||
try {
|
||||
Reward reward = entryParser.parseReward(rewardMap);
|
||||
rewards.add(reward);
|
||||
} catch (ConfigParseException e) {
|
||||
throw new ConfigParseException(
|
||||
"Error parsing reward at index " + i + ": " + e.getMessage(),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return rewards;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package dev.loki.lomines.data.reward;
|
||||
package dev.loki.lomines.data.reward.parse.command;
|
||||
|
||||
import dev.loki.lomines.data.config.parser.ConfigParseException;
|
||||
|
||||
|
|
@ -9,10 +9,10 @@ import java.util.Map;
|
|||
/**
|
||||
* Parses reward commands from configuration.
|
||||
*/
|
||||
final class RewardCommandParser {
|
||||
public final class RewardCommandParser {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> parseCommands(Map<String, Object> rewardMap) throws ConfigParseException {
|
||||
public List<String> parseCommands(Map<String, Object> rewardMap) throws ConfigParseException {
|
||||
List<String> commands = new ArrayList<>();
|
||||
if (!rewardMap.containsKey("commands")) {
|
||||
return commands;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package dev.loki.lomines.data.reward;
|
||||
package dev.loki.lomines.data.reward.parse.item;
|
||||
|
||||
import dev.loki.lomines.data.config.parser.ConfigParseException;
|
||||
import dev.loki.lomines.util.ValidationUtils;
|
||||
|
|
@ -12,7 +12,7 @@ import java.util.Map;
|
|||
* Parses ItemStack objects from YAML reward item configurations.
|
||||
* Handles material type, amount, and basic item properties.
|
||||
*/
|
||||
final class RewardItemParser {
|
||||
public final class RewardItemParser {
|
||||
|
||||
/**
|
||||
* Parses a single reward item from a map configuration.
|
||||
|
|
@ -22,7 +22,7 @@ final class RewardItemParser {
|
|||
* @return the parsed ItemStack
|
||||
* @throws ConfigParseException if the item configuration is invalid
|
||||
*/
|
||||
ItemStack parseItem(Map<String, Object> itemMap, int index) throws ConfigParseException {
|
||||
public ItemStack parseItem(Map<String, Object> itemMap, int index) throws ConfigParseException {
|
||||
if (!itemMap.containsKey("type")) {
|
||||
throw new ConfigParseException("Missing required 'type' field in item at index " + index);
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package dev.loki.lomines.data.reward;
|
||||
package dev.loki.lomines.data.reward.parse.item;
|
||||
|
||||
import dev.loki.lomines.data.config.parser.ConfigParseException;
|
||||
import dev.loki.lomines.util.ValidationUtils;
|
||||
|
|
@ -11,10 +11,10 @@ import java.util.Map;
|
|||
/**
|
||||
* Parses reward materials (blocks) from configuration.
|
||||
*/
|
||||
final class RewardMaterialParser {
|
||||
public final class RewardMaterialParser {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Material> parseMaterials(Map<String, Object> rewardMap) throws ConfigParseException {
|
||||
public List<Material> parseMaterials(Map<String, Object> rewardMap) throws ConfigParseException {
|
||||
if (!rewardMap.containsKey("blocks")) {
|
||||
throw new ConfigParseException("Missing required 'blocks' field in reward");
|
||||
}
|
||||
|
|
@ -1,181 +0,0 @@
|
|||
package dev.loki.lomines.data.stats;
|
||||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import dev.lolib.scheduler.ScheduledTask;
|
||||
import dev.lolib.scheduler.Scheduler;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Manages player statistics for all mines.
|
||||
* Handles loading, saving, and auto-saving of player statistics.
|
||||
*/
|
||||
public final class StatsManager {
|
||||
|
||||
private final Map<UUID, PlayerStats> stats = new ConcurrentHashMap<>();
|
||||
private final LoMinesPlugin plugin;
|
||||
private final Path statsFile;
|
||||
private final Leaderboard leaderboard;
|
||||
private ScheduledTask saveTask;
|
||||
|
||||
public StatsManager(LoMinesPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.statsFile = plugin.getDataFolder().toPath().resolve("stats.yml");
|
||||
this.leaderboard = new Leaderboard(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads statistics from file.
|
||||
*/
|
||||
public void load() throws IOException {
|
||||
if (!Files.exists(statsFile)) {
|
||||
plugin.loLogger().info("Stats file does not exist, starting with empty statistics");
|
||||
return;
|
||||
}
|
||||
|
||||
File file = statsFile.toFile();
|
||||
YamlConfiguration yaml = YamlConfiguration.loadConfiguration(file);
|
||||
|
||||
ConfigurationSection playersSection = yaml.getConfigurationSection("players");
|
||||
if (playersSection == null) {
|
||||
plugin.loLogger().info("No players section found in stats.yml");
|
||||
return;
|
||||
}
|
||||
|
||||
for (String uuidString : playersSection.getKeys(false)) {
|
||||
try {
|
||||
UUID playerId = UUID.fromString(uuidString);
|
||||
ConfigurationSection playerSection = playersSection.getConfigurationSection(uuidString);
|
||||
|
||||
if (playerSection == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
PlayerStats playerStats = getOrCreate(playerId);
|
||||
|
||||
// Load total blocks
|
||||
long totalBlocks = playerSection.getLong("total", 0);
|
||||
playerStats.setTotalBlocks(totalBlocks);
|
||||
|
||||
// Load mine-specific stats
|
||||
ConfigurationSection minesSection = playerSection.getConfigurationSection("mines");
|
||||
if (minesSection != null) {
|
||||
for (String mineName : minesSection.getKeys(false)) {
|
||||
long mineBlocks = minesSection.getLong(mineName, 0);
|
||||
playerStats.setMineBlocks(mineName, mineBlocks);
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
plugin.loLogger().warn("Invalid UUID in stats.yml: " + uuidString);
|
||||
}
|
||||
}
|
||||
|
||||
plugin.loLogger().info("Loaded statistics for " + stats.size() + " player(s)");
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves statistics to file.
|
||||
*/
|
||||
public void save() throws IOException {
|
||||
YamlConfiguration yaml = new YamlConfiguration();
|
||||
|
||||
for (Map.Entry<UUID, PlayerStats> entry : stats.entrySet()) {
|
||||
UUID playerId = entry.getKey();
|
||||
PlayerStats playerStats = entry.getValue();
|
||||
|
||||
String path = "players." + playerId.toString();
|
||||
|
||||
// Save total blocks
|
||||
yaml.set(path + ".total", playerStats.getTotalBlocks());
|
||||
|
||||
// Save mine-specific stats
|
||||
Map<String, Long> mineStats = playerStats.getMineStatsSnapshot();
|
||||
if (!mineStats.isEmpty()) {
|
||||
for (Map.Entry<String, Long> mineEntry : mineStats.entrySet()) {
|
||||
yaml.set(path + ".mines." + mineEntry.getKey(), mineEntry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure parent directory exists
|
||||
if (!Files.exists(statsFile.getParent())) {
|
||||
Files.createDirectories(statsFile.getParent());
|
||||
}
|
||||
|
||||
yaml.save(statsFile.toFile());
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts auto-save task.
|
||||
*/
|
||||
public void startAutoSave() {
|
||||
// Auto-save every 5 minutes (20 ticks/second * 60 seconds * 5 minutes)
|
||||
long interval = 20L * 60L * 5L;
|
||||
|
||||
saveTask = Scheduler.get(plugin).runTimerAsync(
|
||||
() -> {
|
||||
try {
|
||||
save();
|
||||
plugin.loLogger().info("Statistics auto-saved");
|
||||
} catch (IOException e) {
|
||||
plugin.loLogger().error("Failed to auto-save statistics: " + e.getMessage());
|
||||
}
|
||||
},
|
||||
interval,
|
||||
interval
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops auto-save task.
|
||||
*/
|
||||
public void stopAutoSave() {
|
||||
if (saveTask != null) {
|
||||
saveTask.cancel();
|
||||
saveTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or creates player statistics.
|
||||
*/
|
||||
public PlayerStats getOrCreate(UUID playerId) {
|
||||
return stats.computeIfAbsent(playerId, PlayerStats::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments block count for a player in a specific mine.
|
||||
*/
|
||||
public void incrementBlocks(UUID playerId, String mineName) {
|
||||
PlayerStats playerStats = getOrCreate(playerId);
|
||||
playerStats.incrementTotal();
|
||||
playerStats.incrementMine(mineName);
|
||||
|
||||
// Invalidate leaderboard cache
|
||||
leaderboard.invalidateCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all player statistics.
|
||||
*/
|
||||
public Collection<PlayerStats> getAllStats() {
|
||||
return Collections.unmodifiableCollection(stats.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the leaderboard instance.
|
||||
*/
|
||||
public Leaderboard getLeaderboard() {
|
||||
return leaderboard;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
package dev.loki.lomines.data.stats;
|
||||
package dev.loki.lomines.data.stats.model;
|
||||
|
||||
import dev.loki.lomines.data.stats.service.StatsManager;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package dev.loki.lomines.data.stats;
|
||||
package dev.loki.lomines.data.stats.model;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
|
@ -16,12 +16,21 @@ public record LeaderboardEntry(UUID playerId, long count) {
|
|||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
LeaderboardEntry that = (LeaderboardEntry) o;
|
||||
return count == that.count && playerId.equals(that.playerId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(playerId, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LeaderboardEntry{playerId=" + playerId + ", count=" + count + "}";
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package dev.loki.lomines.data.stats;
|
||||
package dev.loki.lomines.data.stats.model;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
package dev.loki.lomines.data.stats.service;
|
||||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import dev.loki.lomines.data.stats.model.Leaderboard;
|
||||
import dev.loki.lomines.data.stats.model.PlayerStats;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public final class StatsManager {
|
||||
|
||||
private final Map<UUID, PlayerStats> stats = new ConcurrentHashMap<>();
|
||||
private final LoMinesPlugin plugin;
|
||||
private final Leaderboard leaderboard;
|
||||
private final StatsPersistence persistence;
|
||||
|
||||
public StatsManager(LoMinesPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.leaderboard = new Leaderboard(this);
|
||||
this.persistence = new StatsPersistence(plugin, this);
|
||||
}
|
||||
|
||||
public StatsPersistence getPersistence() {
|
||||
return persistence;
|
||||
}
|
||||
|
||||
public void load() throws IOException {
|
||||
persistence.load(stats);
|
||||
}
|
||||
|
||||
public void save() throws IOException {
|
||||
persistence.save(stats);
|
||||
}
|
||||
|
||||
public void startAutoSave() {
|
||||
persistence.startAutoSave(() -> {
|
||||
try {
|
||||
save();
|
||||
plugin.getLogger().info("Statistics auto-saved");
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().severe("Failed to auto-save statistics: " + e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void stopAutoSave() {
|
||||
persistence.stopAutoSave();
|
||||
}
|
||||
|
||||
public PlayerStats getOrCreate(UUID playerId) {
|
||||
return stats.computeIfAbsent(playerId, PlayerStats::new);
|
||||
}
|
||||
|
||||
public void incrementBlocks(UUID playerId, String mineName) {
|
||||
PlayerStats playerStats = getOrCreate(playerId);
|
||||
playerStats.incrementTotal();
|
||||
playerStats.incrementMine(mineName);
|
||||
leaderboard.invalidateCache();
|
||||
}
|
||||
|
||||
public Collection<PlayerStats> getAllStats() {
|
||||
return Collections.unmodifiableCollection(stats.values());
|
||||
}
|
||||
|
||||
public Leaderboard getLeaderboard() {
|
||||
return leaderboard;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
package dev.loki.lomines.data.stats.service;
|
||||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import dev.loki.lomines.data.stats.model.PlayerStats;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class StatsPersistence {
|
||||
|
||||
private final LoMinesPlugin plugin;
|
||||
private final Path statsFile;
|
||||
private final StatsManager statsManager;
|
||||
private BukkitTask saveTask;
|
||||
|
||||
public StatsPersistence(LoMinesPlugin plugin, StatsManager statsManager) {
|
||||
this.plugin = plugin;
|
||||
this.statsFile = plugin.getDataFolder().toPath().resolve("stats.yml");
|
||||
this.statsManager = statsManager;
|
||||
}
|
||||
|
||||
public void load(Map<UUID, PlayerStats> stats) throws IOException {
|
||||
if (!Files.exists(statsFile)) {
|
||||
plugin.getLogger().info("Stats file does not exist, starting with empty statistics");
|
||||
return;
|
||||
}
|
||||
|
||||
File file = statsFile.toFile();
|
||||
YamlConfiguration yaml = YamlConfiguration.loadConfiguration(file);
|
||||
|
||||
ConfigurationSection playersSection = yaml.getConfigurationSection("players");
|
||||
if (playersSection == null) {
|
||||
plugin.getLogger().info("No players section found in stats.yml");
|
||||
return;
|
||||
}
|
||||
|
||||
for (String uuidString : playersSection.getKeys(false)) {
|
||||
try {
|
||||
UUID playerId = UUID.fromString(uuidString);
|
||||
ConfigurationSection playerSection = playersSection.getConfigurationSection(uuidString);
|
||||
|
||||
if (playerSection == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
PlayerStats playerStats = stats.computeIfAbsent(playerId, PlayerStats::new);
|
||||
|
||||
long totalBlocks = playerSection.getLong("total", 0);
|
||||
playerStats.setTotalBlocks(totalBlocks);
|
||||
|
||||
ConfigurationSection minesSection = playerSection.getConfigurationSection("mines");
|
||||
if (minesSection != null) {
|
||||
for (String mineName : minesSection.getKeys(false)) {
|
||||
long mineBlocks = minesSection.getLong(mineName, 0);
|
||||
playerStats.setMineBlocks(mineName, mineBlocks);
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
plugin.getLogger().warning("Invalid UUID in stats.yml: " + uuidString);
|
||||
}
|
||||
}
|
||||
|
||||
plugin.getLogger().info("Loaded statistics for " + stats.size() + " player(s)");
|
||||
}
|
||||
|
||||
public void save(Map<UUID, PlayerStats> stats) throws IOException {
|
||||
YamlConfiguration yaml = new YamlConfiguration();
|
||||
|
||||
for (Map.Entry<UUID, PlayerStats> entry : stats.entrySet()) {
|
||||
UUID playerId = entry.getKey();
|
||||
PlayerStats playerStats = entry.getValue();
|
||||
|
||||
String path = "players." + playerId.toString();
|
||||
yaml.set(path + ".total", playerStats.getTotalBlocks());
|
||||
|
||||
Map<String, Long> mineStats = playerStats.getMineStatsSnapshot();
|
||||
if (!mineStats.isEmpty()) {
|
||||
for (Map.Entry<String, Long> mineEntry : mineStats.entrySet()) {
|
||||
yaml.set(path + ".mines." + mineEntry.getKey(), mineEntry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!Files.exists(statsFile.getParent())) {
|
||||
Files.createDirectories(statsFile.getParent());
|
||||
}
|
||||
|
||||
yaml.save(statsFile.toFile());
|
||||
}
|
||||
|
||||
public void startAutoSave(Runnable saveAction) {
|
||||
long interval = 20L * 60L * 5L;
|
||||
saveTask = Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, saveAction, interval, interval);
|
||||
}
|
||||
|
||||
public void stopAutoSave() {
|
||||
if (saveTask != null) {
|
||||
saveTask.cancel();
|
||||
saveTask = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue