diff --git a/src/main/java/dev/loki/lomines/data/config/ConfigLoader.java b/src/main/java/dev/loki/lomines/data/config/ConfigLoader.java
index f5cb219..7abaeb7 100644
--- a/src/main/java/dev/loki/lomines/data/config/ConfigLoader.java
+++ b/src/main/java/dev/loki/lomines/data/config/ConfigLoader.java
@@ -1,111 +1,427 @@
package dev.loki.lomines.data.config;
-import dev.loki.lomines.data.config.parser.ConfigParseException;
-import dev.loki.lomines.data.config.parser.ConfigParser;
-import dev.loki.lomines.data.reward.Reward;
-import dev.loki.lomines.data.reward.RewardParser;
+import dev.loki.lomines.data.config.block.BlockConfig;
+import dev.loki.lomines.data.config.block.BlockKey;
+import dev.loki.lomines.data.config.block.FillMode;
+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.reward.RewardConfig.RewardEntry;
+import dev.loki.lomines.data.config.reward.RewardConfig.RewardEntry.ItemReward;
+import dev.loki.lomines.data.config.teleport.TeleportConfig;
+import dev.loki.lomines.data.config.ui.UIConfig;
+import dev.loki.lomines.util.location.Cuboid;
+import dev.loki.lomines.util.location.LocationParser;
+import org.bukkit.Bukkit;
import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.World;
+import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import java.io.IOException;
+import java.nio.file.Files;
import java.nio.file.Path;
-import java.util.List;
-import java.util.Map;
+import java.time.Duration;
+import java.util.*;
/**
- * Facade for loading and saving MineConfig objects from/to YAML files.
- * Coordinates the work between ConfigParser, ConfigValidator, and ConfigSerializer.
- * Follows the Single Responsibility Principle by delegating specific tasks.
+ * Clean config loader for section-based configuration.
+ * Supports YAML inheritance from defaults.
*/
public final class ConfigLoader {
- private final ConfigParser parser;
- private final ConfigValidator validator;
- private final ConfigSerializer serializer;
+ private final Path dataFolder;
+ private YamlConfiguration defaults;
- public ConfigLoader() {
- RewardParser rewardParser = new RewardParser();
- this.parser = new ConfigParser(rewardParser);
- this.validator = new ConfigValidator();
- this.serializer = new ConfigSerializer();
+ public ConfigLoader(Path dataFolder) {
+ this.dataFolder = dataFolder;
+ loadDefaults();
}
/**
- * Loads a MineConfig from a YAML file.
- * Parses the file, validates all data, and builds the MineConfig object.
- *
- * @param configFile the path to the YAML configuration file
- * @return the loaded MineConfig object
- * @throws IOException if the file cannot be read
- * @throws ConfigParseException if the configuration is invalid
+ * Load or create defaults.yml.
*/
- public MineConfig load(Path configFile) throws IOException, ConfigParseException {
- if (configFile == null) {
- throw new ConfigParseException("Config file path cannot be null");
+ private void loadDefaults() {
+ Path defaultsPath = dataFolder.resolve("mines").resolve("_defaults.yml");
+ defaults = new YamlConfiguration();
+
+ if (Files.exists(defaultsPath)) {
+ defaults = YamlConfiguration.loadConfiguration(defaultsPath.toFile());
+ } else {
+ // Create defaults with sensible values
+ 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", "{mine} {percent}% ({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());
+ }
+
+ /**
+ * 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)) {
+ throw new ConfigLoadException("Mine not found: " + mineName);
}
- if (!configFile.toFile().exists()) {
- throw new IOException("Config file does not exist: " + configFile);
+ YamlConfiguration yaml = YamlConfiguration.loadConfiguration(configPath.toFile());
+
+ // Merge with defaults (YAML inheritance)
+ mergeDefaults(yaml);
+
+ try {
+ return MineConfig.builder(mineName)
+ .region(parseRegion(yaml))
+ .blocks(parseBlocks(yaml))
+ .reset(parseReset(yaml))
+ .rewards(parseRewards(yaml))
+ .teleport(parseTeleport(yaml))
+ .ui(parseUI(yaml))
+ .build();
+ } catch (Exception e) {
+ throw new ConfigLoadException("Failed to load mine '" + mineName + "': " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 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();
+
+ // Save regions
+ saveRegion(yaml, config.region());
+
+ // Save blocks
+ saveBlocks(yaml, config.blocks());
+
+ // Save reset
+ saveReset(yaml, config.reset());
+
+ // Save rewards
+ saveRewards(yaml, config.rewards());
+
+ // Save teleport
+ saveTeleport(yaml, config.teleport());
+
+ // Save UI
+ saveUI(yaml, config.ui());
+
+ try {
+ Files.createDirectories(configPath.getParent());
+ yaml.save(configPath.toFile());
+ } catch (IOException e) {
+ throw new ConfigLoadException("Failed to save mine: " + e.getMessage(), e);
+ }
+ }
+
+ // --- Parsers ---
+
+ private RegionConfig parseRegion(YamlConfiguration yaml) {
+ List selections = new ArrayList<>();
+
+ for (int i = 1; i <= 10; i++) {
+ String key = "region.selection." + i;
+ if (yaml.contains(key)) {
+ String locStr = yaml.getString(key);
+ if (locStr != null && !locStr.isBlank()) {
+ selections.add(parseLocation(locStr));
+ }
+ }
}
- YamlConfiguration yaml = YamlConfiguration.loadConfiguration(configFile.toFile());
+ if (selections.isEmpty()) {
+ // Legacy fallback
+ for (int i = 1; i <= 10; i++) {
+ String key = "selection." + i;
+ if (yaml.contains(key)) {
+ String locStr = yaml.getString(key);
+ if (locStr != null && !locStr.isBlank()) {
+ selections.add(parseLocation(locStr));
+ }
+ }
+ }
+ }
- // Parse all sections
- List selections = parser.parseSelections(yaml);
- Map blocks = parser.parseBlocks(yaml);
- FillMode fillMode = parser.parseFillMode(yaml);
- String maskMarker = parser.parseMaskMarker(yaml);
- List maskPositions = parser.parseMaskPositions(yaml);
- List rewards = parser.parseRewards(yaml);
- Location teleportLocation = parser.parseTeleportLocation(yaml);
+ if (selections.size() < 2) {
+ throw new IllegalArgumentException("Mine must have at least 2 selection points (1 region)");
+ }
- // Validate all data
- validator.validateSelections(selections);
- validator.validateBlocks(blocks);
- validator.validateMaskMarker(maskMarker);
- validator.validateRewards(rewards);
+ return RegionConfig.fromSelections(selections);
+ }
- // Build and return config
- return MineConfig.builder()
- .selections(selections)
- .blocks(blocks)
- .fillMode(fillMode)
- .maskMarkerMaterial(maskMarker)
- .maskPositions(maskPositions)
- .rewards(rewards)
- .resetTicks(yaml.getInt("reset.ticks", 6000))
- .resetPercent(yaml.getDouble("reset.percent", 10.0))
- .resetOnPercentEnabled(yaml.getBoolean("reset-on-percent", false))
- .actionBarEnabled(yaml.getBoolean("actionbar.enabled", true))
- .actionBarMessage(yaml.getString("actionbar.message", ""))
- .actionBarRange(yaml.getDouble("actionbar.range", 50.0))
- .timerFormat(yaml.getString("timer-format", "mm:ss"))
- .teleportOnReset(yaml.getBoolean("teleport-on-reset", false))
- .teleportLocation(teleportLocation)
- .resetCommands(yaml.getStringList("reset-commands"))
- .broadcastReset(yaml.getString("broadcast-reset", ""))
+ private BlockConfig parseBlocks(YamlConfiguration yaml) {
+ ConfigurationSection contents = yaml.getConfigurationSection("blocks.contents");
+ if (contents == null) {
+ contents = yaml.getConfigurationSection("contents"); // Legacy fallback
+ }
+
+ if (contents == null) {
+ throw new IllegalArgumentException("Missing 'blocks.contents' section");
+ }
+
+ Map weights = new HashMap<>();
+ for (String key : contents.getKeys(false)) {
+ double weight = contents.getDouble(key);
+ if (weight <= 0) continue;
+ weights.put(BlockKey.deserialize(key), weight);
+ }
+
+ FillMode fillMode = FillMode.valueOf(
+ yaml.getString("blocks.fill-mode", "CUBOID").toUpperCase()
+ );
+
+ // Parse mask config if applicable
+ BlockConfig.MaskConfig mask = null;
+ if (fillMode == FillMode.MASK) {
+ String markerStr = yaml.getString("blocks.mask.marker", "pink_concrete");
+ BlockKey marker = BlockKey.deserialize(markerStr);
+
+ Map positions = new HashMap<>();
+ List posList = yaml.getStringList("blocks.mask.positions");
+ for (String pos : posList) {
+ positions.put(pos, true);
+ }
+
+ mask = new BlockConfig.MaskConfig(marker, positions);
+ }
+
+ return new BlockConfig(weights, fillMode, mask);
+ }
+
+ private ResetConfig parseReset(YamlConfiguration yaml) {
+ String intervalStr = yaml.getString("reset.interval", defaults.getString("reset.interval", "5m"));
+
+ return ResetConfig.builder()
+ .interval(intervalStr)
+ .percentTrigger(yaml.getDouble("reset.percent-trigger", defaults.getDouble("reset.percent-trigger", 10.0)))
+ .percentEnabled(yaml.getBoolean("reset.percent-enabled", defaults.getBoolean("reset.percent-enabled", false)))
+ .commands(yaml.getStringList("reset.commands"))
+ .broadcastMessage(yaml.getString("reset.broadcast", ""))
.build();
}
- /**
- * Saves a MineConfig to a YAML file.
- * Serializes the config and writes it to the specified file.
- *
- * @param config the MineConfig to save
- * @param configFile the path to the YAML configuration file
- * @throws IOException if the file cannot be written
- * @throws IllegalArgumentException if config or configFile is null
- */
- public void save(MineConfig config, Path configFile) throws IOException {
- if (config == null) {
- throw new IllegalArgumentException("Config cannot be null");
- }
- if (configFile == null) {
- throw new IllegalArgumentException("Config file path cannot be null");
+ private RewardConfig parseRewards(YamlConfiguration yaml) {
+ List entries = new ArrayList<>();
+ List