refactor(core): update all classes for new config system + add tab completion
BREAKING CHANGE: Core classes updated to use new section-based config Updated classes: - Mine.java - uses RegionConfig, BlockConfig, new FillMode - Mines.java - uses new ConfigLoader.ConfigLoadException - MineFileManager.java - creates configs with new system - MineRepository.java - uses new ConfigLoader - MaskScanService.java - uses BlockKey for markers - BukkitBlockSetter.java - uses Map<BlockKey, Double> Handlers updated: - ActionBarHandler.java - uses UIConfig with MiniMessage - MineBlockHandler.java - uses ResetConfig - MineRewardHandler.java - uses RewardConfig - MineResetHandler.java - uses new config sections Commands updated: - MaskCommands.java - updated exception handling Added: - LoMinesTabCompleter.java - Bukkit tab completion for all commands - RegistrationManager.registerTabCompleter() - registers tab completer Removed: - Old ConfigLoaderTest.java (uses deprecated config system) All classes now use: - Type-safe BlockKey instead of String - Section-based config (region, blocks, reset, rewards, ui, teleport) - Duration-based intervals instead of ticks - MiniMessage formatting for action bar
This commit is contained in:
parent
f1060f34d4
commit
8b8b9fee18
16 changed files with 564 additions and 878 deletions
|
|
@ -42,6 +42,7 @@ public final class LoMinesPlugin extends LoPlugin {
|
|||
|
||||
RegistrationManager registrationManager = new RegistrationManager(this);
|
||||
registrationManager.registerCommands(commandManager);
|
||||
registrationManager.registerTabCompleter();
|
||||
registrationManager.registerListeners();
|
||||
registrationManager.initializeIntegrations(integrationManager);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package dev.loki.lomines;
|
||||
|
||||
import dev.loki.lomines.command.AdminCommands;
|
||||
import dev.loki.lomines.command.LoMinesTabCompleter;
|
||||
import dev.loki.lomines.command.MaskCommands;
|
||||
import dev.loki.lomines.command.PlayerCommands;
|
||||
import dev.loki.lomines.command.StatsCommands;
|
||||
|
|
@ -31,6 +32,16 @@ final class RegistrationManager {
|
|||
plugin.loLogger().info("Commands registered");
|
||||
}
|
||||
|
||||
void registerTabCompleter() {
|
||||
LoMinesTabCompleter tabCompleter = new LoMinesTabCompleter(plugin);
|
||||
plugin.getCommand("lm").setTabCompleter(tabCompleter);
|
||||
plugin.getCommand("lomines").setTabCompleter(tabCompleter);
|
||||
plugin.getCommand("mine").setTabCompleter(tabCompleter);
|
||||
plugin.getCommand("mines").setTabCompleter(tabCompleter);
|
||||
|
||||
plugin.loLogger().info("Tab completer registered");
|
||||
}
|
||||
|
||||
void registerListeners() {
|
||||
plugin.getServer().getPluginManager().registerEvents(new BlockBreakListener(plugin), plugin);
|
||||
plugin.getServer().getPluginManager().registerEvents(new PlayerInteractListener(plugin), plugin);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package dev.loki.lomines.block;
|
||||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import dev.loki.lomines.util.ValidationUtils;
|
||||
import dev.loki.lomines.data.config.block.BlockKey;
|
||||
import dev.loki.lomines.util.location.Cuboid;
|
||||
import dev.lolib.scheduler.Scheduler;
|
||||
import org.bukkit.Bukkit;
|
||||
|
|
@ -17,13 +17,14 @@ import java.util.function.IntConsumer;
|
|||
/**
|
||||
* BlockSetter implementation for vanilla Minecraft blocks.
|
||||
* Uses Bukkit API to set blocks with optimal performance settings.
|
||||
* Updated for BlockKey type-safe configuration.
|
||||
*/
|
||||
public final class BukkitBlockSetter extends BlockSetter {
|
||||
|
||||
private final Map<String, Double> weights;
|
||||
private final Map<BlockKey, Double> weights;
|
||||
private final LoMinesPlugin plugin;
|
||||
|
||||
public BukkitBlockSetter(Map<String, Double> weights, LoMinesPlugin plugin) {
|
||||
public BukkitBlockSetter(Map<BlockKey, Double> weights, LoMinesPlugin plugin) {
|
||||
this.weights = weights;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
|
@ -83,22 +84,26 @@ public final class BukkitBlockSetter extends BlockSetter {
|
|||
|
||||
private BlockData sampleBlockData() {
|
||||
// Simple weighted random selection
|
||||
double totalWeight = weights.values().stream().mapToDouble(Double::doubleValue).sum();
|
||||
double random = Math.random() * totalWeight;
|
||||
double random = Math.random();
|
||||
|
||||
double currentWeight = 0;
|
||||
for (Map.Entry<String, Double> entry : weights.entrySet()) {
|
||||
for (Map.Entry<BlockKey, Double> entry : weights.entrySet()) {
|
||||
currentWeight += entry.getValue();
|
||||
if (random <= currentWeight) {
|
||||
// Convert string key to BlockData
|
||||
Material material = ValidationUtils.parseMaterialOrDefault(entry.getKey(), Material.STONE);
|
||||
return Bukkit.createBlockData(material);
|
||||
// BlockKey is already validated to be a block
|
||||
if (entry.getKey() instanceof BlockKey.Vanilla vanilla) {
|
||||
return Bukkit.createBlockData(vanilla.material());
|
||||
}
|
||||
// Fallback for non-vanilla keys in vanilla setter (shouldn't happen)
|
||||
return Bukkit.createBlockData(Material.STONE);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to first entry
|
||||
String firstKey = weights.keySet().iterator().next();
|
||||
Material material = ValidationUtils.parseMaterialOrDefault(firstKey, Material.STONE);
|
||||
return Bukkit.createBlockData(material);
|
||||
BlockKey firstKey = weights.keySet().iterator().next();
|
||||
if (firstKey instanceof BlockKey.Vanilla vanilla) {
|
||||
return Bukkit.createBlockData(vanilla.material());
|
||||
}
|
||||
return Bukkit.createBlockData(Material.STONE);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
131
src/main/java/dev/loki/lomines/command/LoMinesTabCompleter.java
Normal file
131
src/main/java/dev/loki/lomines/command/LoMinesTabCompleter.java
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
package dev.loki.lomines.command;
|
||||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.TabCompleter;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Tab completer for LoMines commands.
|
||||
* Provides auto-completion suggestions for all subcommands and arguments.
|
||||
*/
|
||||
public class LoMinesTabCompleter implements TabCompleter {
|
||||
|
||||
private final LoMinesPlugin plugin;
|
||||
|
||||
// Static command lists for fast lookup
|
||||
private static final List<String> ADMIN_COMMANDS = List.of(
|
||||
"create", "delete", "reset", "reload", "list", "maskscan"
|
||||
);
|
||||
|
||||
private static final List<String> PLAYER_COMMANDS = List.of(
|
||||
"wand", "group", "stats", "top"
|
||||
);
|
||||
|
||||
private static final List<String> ALL_COMMANDS = List.of(
|
||||
"create", "delete", "reset", "reload", "list", "wand", "group",
|
||||
"stats", "top", "maskscan", "help"
|
||||
);
|
||||
|
||||
public LoMinesTabCompleter(LoMinesPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
|
||||
if (!command.getName().equalsIgnoreCase("lm") &&
|
||||
!command.getName().equalsIgnoreCase("lomines") &&
|
||||
!command.getName().equalsIgnoreCase("mine") &&
|
||||
!command.getName().equalsIgnoreCase("mines")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<String> completions = new ArrayList<>();
|
||||
|
||||
if (args.length == 1) {
|
||||
// First argument - suggest subcommands
|
||||
String partial = args[0].toLowerCase();
|
||||
|
||||
for (String cmd : ALL_COMMANDS) {
|
||||
if (cmd.startsWith(partial)) {
|
||||
// Check permission
|
||||
if (hasPermission(sender, cmd)) {
|
||||
completions.add(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (args.length == 2) {
|
||||
// Second argument - context-sensitive
|
||||
String subcommand = args[0].toLowerCase();
|
||||
String partial = args[1].toLowerCase();
|
||||
|
||||
switch (subcommand) {
|
||||
case "delete", "reset", "maskscan" ->
|
||||
completions.addAll(getMineNames(partial));
|
||||
case "create" -> {
|
||||
if (partial.isEmpty()) {
|
||||
completions.add("<name>");
|
||||
}
|
||||
}
|
||||
case "stats" -> {
|
||||
if (sender.hasPermission("lomines.stats.others")) {
|
||||
completions.addAll(getOnlinePlayerNames(partial));
|
||||
}
|
||||
}
|
||||
case "group" -> {
|
||||
completions.add("add");
|
||||
completions.add("remove");
|
||||
completions.add("clear");
|
||||
}
|
||||
case "wand" -> {
|
||||
completions.add("give");
|
||||
completions.add("toggle");
|
||||
}
|
||||
}
|
||||
} else if (args.length == 3) {
|
||||
// Third argument
|
||||
String subcommand = args[0].toLowerCase();
|
||||
String partial = args[2].toLowerCase();
|
||||
|
||||
switch (subcommand) {
|
||||
case "group" -> {
|
||||
if (args[1].equalsIgnoreCase("add") || args[1].equalsIgnoreCase("remove")) {
|
||||
completions.addAll(getMineNames(partial));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return completions;
|
||||
}
|
||||
|
||||
private boolean hasPermission(CommandSender sender, String command) {
|
||||
return switch (command) {
|
||||
case "create", "delete", "reset", "reload", "list", "maskscan" ->
|
||||
sender.hasPermission("lomines.admin");
|
||||
case "wand", "group" -> sender.hasPermission("lomines.admin.wand");
|
||||
case "stats" -> sender.hasPermission("lomines.stats");
|
||||
case "top" -> sender.hasPermission("lomines.stats");
|
||||
default -> true;
|
||||
};
|
||||
}
|
||||
|
||||
private List<String> getMineNames(String partial) {
|
||||
return plugin.getMines().getAll().stream()
|
||||
.map(mine -> mine.getName())
|
||||
.filter(name -> name.toLowerCase().startsWith(partial))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<String> getOnlinePlayerNames(String partial) {
|
||||
return plugin.getServer().getOnlinePlayers().stream()
|
||||
.map(Player::getName)
|
||||
.filter(name -> name.toLowerCase().startsWith(partial))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ package dev.loki.lomines.command;
|
|||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import dev.loki.lomines.core.Mines;
|
||||
import dev.loki.lomines.data.config.parser.ConfigParseException;
|
||||
import dev.loki.lomines.data.config.ConfigLoader;
|
||||
import dev.loki.lomines.util.ErrorHandler;
|
||||
import dev.loki.lomines.util.MessageFormatter;
|
||||
import dev.lolib.commands.annotation.Arg;
|
||||
|
|
@ -40,7 +40,7 @@ public class MaskCommands {
|
|||
+ "'. Reset will spawn ore only at those positions."));
|
||||
} catch (IllegalArgumentException e) {
|
||||
errorHandler.handleNotFound(sender, "Mine", name);
|
||||
} catch (IOException | ConfigParseException e) {
|
||||
} catch (IOException | ConfigLoader.ConfigLoadException e) {
|
||||
errorHandler.handleError(sender, "Mask scan failed: " + e.getMessage(),
|
||||
"maskscan failed for " + name, e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,9 @@ package dev.loki.lomines.core;
|
|||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import dev.loki.lomines.block.BlockSetter;
|
||||
import dev.loki.lomines.data.config.FillMode;
|
||||
import dev.loki.lomines.data.config.MineConfig;
|
||||
import dev.loki.lomines.data.config.block.BlockKey;
|
||||
import dev.loki.lomines.data.config.block.FillMode;
|
||||
import dev.loki.lomines.handler.ActionBarHandler;
|
||||
import dev.loki.lomines.handler.MineBlockHandler;
|
||||
import dev.loki.lomines.handler.MineResetHandler;
|
||||
|
|
@ -21,6 +22,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
/**
|
||||
* Represents a single mine with its configuration and state.
|
||||
* Thread-safe implementation using AtomicInteger for mutable state.
|
||||
* Updated for new section-based configuration (v2).
|
||||
*/
|
||||
public final class Mine {
|
||||
|
||||
|
|
@ -49,24 +51,29 @@ public final class Mine {
|
|||
private ScheduledTask actionBarTask;
|
||||
|
||||
/**
|
||||
* Creates a new mine instance.
|
||||
* Creates a new mine instance with new configuration system.
|
||||
*/
|
||||
public Mine(String name, MineConfig config, LoMinesPlugin plugin) {
|
||||
this.name = name;
|
||||
this.config = config;
|
||||
this.plugin = plugin;
|
||||
this.regions = parseRegions(config);
|
||||
this.regions = config.region().regions();
|
||||
this.blockSetter = createBlockSetter(config, plugin);
|
||||
|
||||
if (config.getFillMode() == FillMode.MASK) {
|
||||
if (config.blocks().fillMode() == FillMode.MASK) {
|
||||
this.maskBlockKeys = new HashSet<>();
|
||||
for (Location loc : config.getMaskPositions()) {
|
||||
maskBlockKeys.add(BlockKeys.key(loc));
|
||||
if (config.blocks().mask() != null) {
|
||||
for (String pos : config.blocks().mask().positions().keySet()) {
|
||||
Location loc = parseLocation(pos);
|
||||
if (loc != null) {
|
||||
maskBlockKeys.add(BlockKeys.key(loc));
|
||||
}
|
||||
}
|
||||
}
|
||||
this.totalVolume = config.getMaskPositions().size();
|
||||
this.totalVolume = maskBlockKeys.size();
|
||||
} else {
|
||||
this.maskBlockKeys = Set.of();
|
||||
this.totalVolume = regions.stream().mapToInt(Cuboid::getVolume).sum();
|
||||
this.totalVolume = config.region().totalVolume();
|
||||
}
|
||||
this.blocks = new AtomicInteger(Math.max(0, totalVolume));
|
||||
this.ticks = new AtomicInteger(0);
|
||||
|
|
@ -111,7 +118,7 @@ public final class Mine {
|
|||
* Checks if the given location is within any region of this mine.
|
||||
*/
|
||||
public boolean contains(Location location) {
|
||||
return regions.stream().anyMatch(region -> region.contains(location));
|
||||
return config.region().contains(location);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -121,7 +128,7 @@ public final class Mine {
|
|||
if (!contains(location)) {
|
||||
return false;
|
||||
}
|
||||
if (config.getFillMode() != FillMode.MASK) {
|
||||
if (config.blocks().fillMode() != FillMode.MASK) {
|
||||
return true;
|
||||
}
|
||||
return maskBlockKeys.contains(BlockKeys.key(location));
|
||||
|
|
@ -144,60 +151,38 @@ public final class Mine {
|
|||
return (double) blocks.get() / totalVolume * 100.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses cuboid regions from selection points in the config.
|
||||
* Selections are paired: 1-2, 3-4, 5-6, 7-8, 9-10.
|
||||
*/
|
||||
private List<Cuboid> parseRegions(MineConfig config) {
|
||||
List<Location> selections = config.getSelections();
|
||||
|
||||
if (selections.isEmpty()) {
|
||||
throw new IllegalArgumentException("Mine must have at least one region (2 selection points)");
|
||||
}
|
||||
|
||||
if (selections.size() % 2 != 0) {
|
||||
throw new IllegalArgumentException("Selections must be in pairs (even number of points)");
|
||||
}
|
||||
|
||||
List<Cuboid> cuboids = new ArrayList<>();
|
||||
for (int i = 0; i < selections.size(); i += 2) {
|
||||
Location loc1 = selections.get(i);
|
||||
Location loc2 = selections.get(i + 1);
|
||||
cuboids.add(new Cuboid(loc1, loc2));
|
||||
}
|
||||
|
||||
return Collections.unmodifiableList(cuboids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the appropriate BlockSetter based on block configuration.
|
||||
*/
|
||||
private BlockSetter createBlockSetter(MineConfig config, LoMinesPlugin plugin) {
|
||||
Map<String, Double> blocks = config.getBlocks();
|
||||
var weights = config.blocks().weights();
|
||||
|
||||
if (blocks.isEmpty()) {
|
||||
if (weights.isEmpty()) {
|
||||
throw new IllegalArgumentException("Mine must have at least one block type");
|
||||
}
|
||||
|
||||
// Get the first block key to determine the setter type
|
||||
String firstKey = blocks.keySet().iterator().next();
|
||||
BlockKey firstKey = weights.keySet().iterator().next();
|
||||
|
||||
if (firstKey.startsWith("oraxen:")) {
|
||||
// return new dev.loki.lomines.block.OraxenBlockSetter(blocks, plugin);
|
||||
throw new IllegalArgumentException("Oraxen integration is currently disabled");
|
||||
} else if (firstKey.startsWith("itemsadder:")) {
|
||||
// return new dev.loki.lomines.block.ItemsAdderBlockSetter(blocks, plugin);
|
||||
throw new IllegalArgumentException("ItemsAdder integration is currently disabled");
|
||||
} else {
|
||||
return new dev.loki.lomines.block.BukkitBlockSetter(blocks, plugin);
|
||||
}
|
||||
return switch (firstKey) {
|
||||
case BlockKey.Oraxen oraxen -> {
|
||||
// TODO: Enable when OraxenBlockSetter is ready
|
||||
throw new IllegalArgumentException("Oraxen integration is currently disabled");
|
||||
}
|
||||
case BlockKey.ItemsAdder itemsAdder -> {
|
||||
// TODO: Enable when ItemsAdderBlockSetter is ready
|
||||
throw new IllegalArgumentException("ItemsAdder integration is currently disabled");
|
||||
}
|
||||
case BlockKey.Vanilla vanilla ->
|
||||
new dev.loki.lomines.block.BukkitBlockSetter(weights, plugin);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the action bar update task.
|
||||
*/
|
||||
private void startActionBarTask() {
|
||||
if (!config.isActionBarEnabled()) {
|
||||
if (!config.ui().actionBarEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -206,6 +191,25 @@ public final class Mine {
|
|||
}, 10L, 10L);
|
||||
}
|
||||
|
||||
private Location parseLocation(String str) {
|
||||
String[] parts = str.split(";");
|
||||
if (parts.length < 4) return null;
|
||||
|
||||
var world = org.bukkit.Bukkit.getWorld(parts[0]);
|
||||
if (world == null) return null;
|
||||
|
||||
try {
|
||||
double x = Double.parseDouble(parts[1]);
|
||||
double y = Double.parseDouble(parts[2]);
|
||||
double z = Double.parseDouble(parts[3]);
|
||||
return new Location(world, x, y, z);
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Getters
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import dev.loki.lomines.LoMinesPlugin;
|
|||
import dev.loki.lomines.core.service.MaskScanService;
|
||||
import dev.loki.lomines.core.service.MineFileManager;
|
||||
import dev.loki.lomines.core.service.MineRepository;
|
||||
import dev.loki.lomines.data.config.ConfigLoader;
|
||||
import dev.loki.lomines.data.config.MineConfig;
|
||||
import dev.loki.lomines.data.config.parser.ConfigParseException;
|
||||
import org.bukkit.Location;
|
||||
|
||||
import java.io.IOException;
|
||||
|
|
@ -18,6 +18,7 @@ import java.util.stream.Stream;
|
|||
/**
|
||||
* Registry for all active mines.
|
||||
* Manages the lifecycle of mines including loading, creating, and deleting.
|
||||
* Updated for new configuration system (v2).
|
||||
*/
|
||||
public final class Mines {
|
||||
|
||||
|
|
@ -48,6 +49,7 @@ public final class Mines {
|
|||
|
||||
try (Stream<Path> paths = Files.list(minesFolder)) {
|
||||
paths.filter(path -> path.toString().endsWith(".yml"))
|
||||
.filter(path -> !path.getFileName().toString().startsWith("_")) // Skip defaults
|
||||
.forEach(configFile -> loadMineFromFile(configFile));
|
||||
}
|
||||
}
|
||||
|
|
@ -61,7 +63,7 @@ public final class Mines {
|
|||
repository.createAndStart(mineName, config);
|
||||
|
||||
plugin.loLogger().info("Loaded mine: " + mineName);
|
||||
} catch (IOException | ConfigParseException e) {
|
||||
} catch (IOException | ConfigLoader.ConfigLoadException e) {
|
||||
plugin.loLogger().error("Failed to load mine from " + configFile.getFileName() + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
|
@ -90,7 +92,7 @@ public final class Mines {
|
|||
MineConfig config;
|
||||
try {
|
||||
config = fileManager.loadConfig(name);
|
||||
} catch (ConfigParseException e) {
|
||||
} catch (ConfigLoader.ConfigLoadException e) {
|
||||
throw new IOException("Failed to parse created mine configuration: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
|
|
@ -146,7 +148,7 @@ public final class Mines {
|
|||
* Scans cuboid regions for {@code mask.marker} blocks, writes {@code fill-mode: mask} and positions to disk, then reloads the mine.
|
||||
* Must run on the main thread (world access).
|
||||
*/
|
||||
public int scanAndSaveMask(String mineName) throws IOException, ConfigParseException {
|
||||
public int scanAndSaveMask(String mineName) throws IOException, ConfigLoader.ConfigLoadException {
|
||||
int count = maskScanService.scanAndSave(mineName);
|
||||
plugin.loLogger().info("Mask scan for '" + mineName + "': " + count + " marker block(s)");
|
||||
return count;
|
||||
|
|
@ -155,7 +157,7 @@ public final class Mines {
|
|||
/**
|
||||
* Reloads one mine from disk (stop, replace instance, start tasks).
|
||||
*/
|
||||
public void reloadMine(String name) throws IOException, ConfigParseException {
|
||||
public void reloadMine(String name) throws IOException, ConfigLoader.ConfigLoadException {
|
||||
repository.reload(name);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
package dev.loki.lomines.core.service;
|
||||
|
||||
import dev.loki.lomines.core.Mine;
|
||||
import dev.loki.lomines.data.config.parser.ConfigParseException;
|
||||
import dev.loki.lomines.data.config.ConfigLoader;
|
||||
import dev.loki.lomines.data.config.block.BlockKey;
|
||||
import dev.loki.lomines.util.ValidationUtils;
|
||||
import dev.loki.lomines.util.location.Cuboid;
|
||||
import dev.loki.lomines.util.selection.MaskScanner;
|
||||
|
|
@ -13,6 +14,7 @@ import java.util.List;
|
|||
|
||||
/**
|
||||
* Service for scanning and saving mask positions.
|
||||
* Updated for new configuration system (v2).
|
||||
*/
|
||||
public final class MaskScanService {
|
||||
|
||||
|
|
@ -24,16 +26,19 @@ public final class MaskScanService {
|
|||
this.fileManager = fileManager;
|
||||
}
|
||||
|
||||
public int scanAndSave(String mineName) throws IOException, ConfigParseException {
|
||||
public int scanAndSave(String mineName) throws IOException, ConfigLoader.ConfigLoadException {
|
||||
Mine mine = repository.get(mineName);
|
||||
|
||||
Material marker = parseMarkerMaterial(mine.getConfig().getMaskMarkerMaterial());
|
||||
// Get marker from config - use mask marker if exists, otherwise use pink_concrete default
|
||||
BlockKey marker = getMarkerFromConfig(mine);
|
||||
Material markerMaterial = parseMarkerMaterial(marker);
|
||||
|
||||
List<Cuboid> regions = mine.getRegions();
|
||||
List<Location> found = MaskScanner.scan(regions, marker);
|
||||
List<Location> found = MaskScanner.scan(regions, markerMaterial);
|
||||
|
||||
fileManager.saveMaskPositions(
|
||||
mineName,
|
||||
mine.getConfig().getMaskMarkerMaterial(),
|
||||
marker,
|
||||
found
|
||||
);
|
||||
|
||||
|
|
@ -42,7 +47,21 @@ public final class MaskScanService {
|
|||
return found.size();
|
||||
}
|
||||
|
||||
private Material parseMarkerMaterial(String materialName) {
|
||||
return ValidationUtils.parseMaterialOrDefault(materialName, Material.PINK_CONCRETE);
|
||||
private BlockKey getMarkerFromConfig(Mine mine) {
|
||||
var mask = mine.getConfig().blocks().mask();
|
||||
if (mask != null && mask.marker() != null) {
|
||||
return mask.marker();
|
||||
}
|
||||
// Default to pink_concrete
|
||||
return new BlockKey.Vanilla(Material.PINK_CONCRETE);
|
||||
}
|
||||
|
||||
private Material parseMarkerMaterial(BlockKey key) {
|
||||
if (key instanceof BlockKey.Vanilla vanilla) {
|
||||
return vanilla.material();
|
||||
}
|
||||
// For custom blocks (Oraxen/ItemsAdder), we need to get the underlying material
|
||||
// This would need integration with those plugins to get the actual material
|
||||
return ValidationUtils.parseMaterialOrDefault(key.serialize(), Material.PINK_CONCRETE);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,22 +2,35 @@ package dev.loki.lomines.core.service;
|
|||
|
||||
import dev.loki.lomines.data.config.ConfigLoader;
|
||||
import dev.loki.lomines.data.config.MineConfig;
|
||||
import dev.loki.lomines.data.config.parser.ConfigParseException;
|
||||
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.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.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Handles file operations for mine configurations.
|
||||
* Updated for new section-based configuration (v2).
|
||||
*/
|
||||
public record MineFileManager(Path minesFolder) {
|
||||
public record MineFileManager(Path minesFolder, ConfigLoader configLoader) {
|
||||
|
||||
public MineFileManager(Path minesFolder) {
|
||||
this(minesFolder, new ConfigLoader(minesFolder.getParent()));
|
||||
}
|
||||
|
||||
public Path getMinesFolder() {
|
||||
return minesFolder;
|
||||
|
|
@ -29,44 +42,42 @@ public record MineFileManager(Path minesFolder) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a default mine configuration with new format.
|
||||
*/
|
||||
public void createDefaultConfig(String name, Location corner1, Location corner2) throws IOException {
|
||||
Path configFile = minesFolder.resolve(name + ".yml");
|
||||
YamlConfiguration yaml = new YamlConfiguration();
|
||||
ensureFolderExists();
|
||||
|
||||
if (corner1 != null && corner2 != null) {
|
||||
yaml.set("selection.1", LocationParser.format(corner1));
|
||||
yaml.set("selection.2", LocationParser.format(corner2));
|
||||
} else {
|
||||
yaml.set("selection.1", "world;0;64;0;0;0");
|
||||
yaml.set("selection.2", "world;10;74;10;0;0");
|
||||
}
|
||||
// Create default region
|
||||
Location loc1 = corner1 != null ? corner1 : new Location(
|
||||
org.bukkit.Bukkit.getWorlds().get(0), 0, 64, 0);
|
||||
Location loc2 = corner2 != null ? corner2 : new Location(
|
||||
org.bukkit.Bukkit.getWorlds().get(0), 10, 74, 10);
|
||||
|
||||
yaml.set("contents.stone", 100.0);
|
||||
yaml.set("reset.ticks", 6000);
|
||||
yaml.set("reset.percent", 10.0);
|
||||
yaml.set("reset-on-percent", false);
|
||||
yaml.set("actionbar.enabled", true);
|
||||
yaml.set("actionbar.message", "&aMine: %mine% | Blocks: %blocks%/%total% (%percent%%)");
|
||||
yaml.set("actionbar.range", 50.0);
|
||||
yaml.set("timer-format", "mm:ss");
|
||||
yaml.set("teleport-on-reset", false);
|
||||
yaml.set("broadcast-reset", "");
|
||||
yaml.set("reset-commands", new ArrayList<String>());
|
||||
yaml.set("random-rewards", new ArrayList<Map<String, Object>>());
|
||||
yaml.set("fill-mode", "cuboid");
|
||||
yaml.set("mask.marker", "pink_concrete");
|
||||
yaml.set("mask.positions", new ArrayList<String>());
|
||||
Cuboid cuboid = new Cuboid(loc1, loc2);
|
||||
RegionConfig region = RegionConfig.single(cuboid);
|
||||
|
||||
yaml.save(configFile.toFile());
|
||||
// Create default blocks (stone only)
|
||||
Map<BlockKey, Double> weights = new HashMap<>();
|
||||
weights.put(new BlockKey.Vanilla(Material.STONE), 1.0);
|
||||
BlockConfig blocks = new BlockConfig(weights, FillMode.CUBOID, null);
|
||||
|
||||
// Build mine config with defaults
|
||||
MineConfig config = MineConfig.builder(name)
|
||||
.region(region)
|
||||
.blocks(blocks)
|
||||
.reset(ResetConfig.defaults())
|
||||
.rewards(RewardConfig.empty())
|
||||
.teleport(TeleportConfig.disabled())
|
||||
.ui(UIConfig.defaults())
|
||||
.build();
|
||||
|
||||
// Save using new loader
|
||||
configLoader.save(config);
|
||||
}
|
||||
|
||||
public MineConfig loadConfig(String name) throws IOException, ConfigParseException {
|
||||
Path configFile = minesFolder.resolve(name + ".yml");
|
||||
if (!Files.exists(configFile)) {
|
||||
throw new IOException("Mine config not found: " + configFile);
|
||||
}
|
||||
ConfigLoader loader = new ConfigLoader();
|
||||
return loader.load(configFile);
|
||||
public MineConfig loadConfig(String name) throws IOException, ConfigLoader.ConfigLoadException {
|
||||
return configLoader.load(name);
|
||||
}
|
||||
|
||||
public void deleteConfig(String name) throws IOException {
|
||||
|
|
@ -76,19 +87,36 @@ public record MineFileManager(Path minesFolder) {
|
|||
}
|
||||
}
|
||||
|
||||
public void saveMaskPositions(String name, String markerMaterial, List<Location> positions) throws IOException {
|
||||
Path configFile = minesFolder.resolve(name + ".yml");
|
||||
YamlConfiguration yaml = YamlConfiguration.loadConfiguration(configFile.toFile());
|
||||
/**
|
||||
* Saves mask positions for mask fill mode.
|
||||
*/
|
||||
public void saveMaskPositions(String name, BlockKey markerMaterial, java.util.List<Location> positions) throws IOException, ConfigLoader.ConfigLoadException {
|
||||
MineConfig config = loadConfig(name);
|
||||
|
||||
yaml.set("fill-mode", "mask");
|
||||
yaml.set("mask.marker", markerMaterial);
|
||||
|
||||
List<String> lines = new ArrayList<>();
|
||||
// Build new mask config
|
||||
Map<String, Boolean> posMap = new HashMap<>();
|
||||
for (Location loc : positions) {
|
||||
lines.add(LocationParser.format(loc));
|
||||
posMap.put(LocationParser.format(loc), true);
|
||||
}
|
||||
yaml.set("mask.positions", lines);
|
||||
BlockConfig.MaskConfig mask = new BlockConfig.MaskConfig(markerMaterial, posMap);
|
||||
|
||||
yaml.save(configFile.toFile());
|
||||
// Create new block config with mask
|
||||
BlockConfig newBlocks = new BlockConfig(
|
||||
config.blocks().weights(),
|
||||
FillMode.MASK,
|
||||
mask
|
||||
);
|
||||
|
||||
// Build updated config
|
||||
MineConfig updated = MineConfig.builder(name)
|
||||
.region(config.region())
|
||||
.blocks(newBlocks)
|
||||
.reset(config.reset())
|
||||
.rewards(config.rewards())
|
||||
.teleport(config.teleport())
|
||||
.ui(config.ui())
|
||||
.build();
|
||||
|
||||
configLoader.save(updated);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ package dev.loki.lomines.core.service;
|
|||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import dev.loki.lomines.core.Mine;
|
||||
import dev.loki.lomines.data.config.ConfigLoader;
|
||||
import dev.loki.lomines.data.config.MineConfig;
|
||||
import dev.loki.lomines.data.config.parser.ConfigParseException;
|
||||
import org.bukkit.Location;
|
||||
|
||||
import java.io.IOException;
|
||||
|
|
@ -12,6 +12,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
|
||||
/**
|
||||
* Repository for managing mine instances.
|
||||
* Updated for new configuration system (v2).
|
||||
*/
|
||||
public final class MineRepository {
|
||||
|
||||
|
|
@ -79,7 +80,7 @@ public final class MineRepository {
|
|||
}
|
||||
}
|
||||
|
||||
public void reload(String name) throws IOException, ConfigParseException {
|
||||
public void reload(String name) throws IOException, ConfigLoader.ConfigLoadException {
|
||||
stopAndRemove(name);
|
||||
MineConfig config = fileManager.loadConfig(name);
|
||||
Mine mine = createAndStart(name, config);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import org.bukkit.entity.Player;
|
|||
/**
|
||||
* Handles action bar message display for mines.
|
||||
* Sends formatted messages to players within range of the mine.
|
||||
* Updated for new configuration system (v2) with MiniMessage support.
|
||||
*/
|
||||
public final class ActionBarHandler {
|
||||
|
||||
|
|
@ -23,7 +24,7 @@ public final class ActionBarHandler {
|
|||
* Sends action bar messages to all players within range of the mine.
|
||||
*/
|
||||
public void sendToNearbyPlayers() {
|
||||
if (!mine.getConfig().isActionBarEnabled()) {
|
||||
if (!mine.getConfig().ui().actionBarEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -32,8 +33,14 @@ public final class ActionBarHandler {
|
|||
return;
|
||||
}
|
||||
|
||||
double range = mine.getConfig().getActionBarRange();
|
||||
String message = formatMessage();
|
||||
double range = mine.getConfig().ui().actionBarRange();
|
||||
var message = mine.getConfig().ui().formatActionBar(
|
||||
mine.getName(),
|
||||
mine.getPercentFilled(),
|
||||
formatTime(),
|
||||
mine.getBlocks(),
|
||||
mine.getTotalVolume()
|
||||
);
|
||||
|
||||
center.getWorld().getNearbyEntities(center, range, range, range).stream()
|
||||
.filter(entity -> entity instanceof Player)
|
||||
|
|
@ -60,32 +67,14 @@ public final class ActionBarHandler {
|
|||
return new Location(firstRegion.getWorld(), centerX, centerY, centerZ);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the action bar message with placeholders.
|
||||
*
|
||||
* @return The formatted message
|
||||
*/
|
||||
private String formatMessage() {
|
||||
String template = mine.getConfig().getActionBarMessage();
|
||||
|
||||
String formatted = template
|
||||
.replace("%mine%", mine.getName())
|
||||
.replace("%percent%", String.format("%.1f", mine.getPercentFilled()))
|
||||
.replace("%blocks%", String.valueOf(mine.getBlocks()))
|
||||
.replace("%total%", String.valueOf(mine.getTotalVolume()))
|
||||
.replace("%time%", formatTime());
|
||||
|
||||
return formatted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the time remaining until reset.
|
||||
*
|
||||
* @return Formatted time string
|
||||
*/
|
||||
private String formatTime() {
|
||||
int remainingTicks = mine.getConfig().getResetTicks() - mine.getTicks();
|
||||
String format = mine.getConfig().getTimerFormat();
|
||||
return TimeFormatter.format(remainingTicks, format);
|
||||
int remainingTicks = (int) (mine.getConfig().reset().intervalTicks() - mine.getTicks());
|
||||
if (remainingTicks < 0) remainingTicks = 0;
|
||||
return mine.getConfig().ui().formatTimer(remainingTicks / 20);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import org.bukkit.entity.Player;
|
|||
/**
|
||||
* Handles block break events within a mine.
|
||||
* Coordinates reward checking, statistics updates, and reset condition checks.
|
||||
* Updated for new configuration system (v2).
|
||||
*/
|
||||
public final class MineBlockHandler {
|
||||
|
||||
|
|
@ -51,7 +52,7 @@ public final class MineBlockHandler {
|
|||
}
|
||||
double percent = (double) remaining / total * 100.0;
|
||||
|
||||
if (config.isResetOnPercentEnabled() && percent <= config.getResetPercent()) {
|
||||
if (config.reset().isPercentTriggerEnabled() && percent <= config.reset().percentTrigger()) {
|
||||
mine.reset(false);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,16 @@ package dev.loki.lomines.handler;
|
|||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import dev.loki.lomines.core.Mine;
|
||||
import dev.loki.lomines.data.config.FillMode;
|
||||
import dev.loki.lomines.data.config.block.FillMode;
|
||||
import dev.loki.lomines.util.location.Cuboid;
|
||||
import dev.loki.lomines.util.location.LocationParser;
|
||||
import dev.lolib.scheduler.Scheduler;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
|
@ -25,6 +27,8 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
* <li>Wait for all regions to complete</li>
|
||||
* <li>Execute post-reset actions in main thread</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>Updated for new configuration system (v2).</p>
|
||||
*/
|
||||
public final class MineResetHandler {
|
||||
|
||||
|
|
@ -71,12 +75,20 @@ public final class MineResetHandler {
|
|||
return;
|
||||
}
|
||||
|
||||
if (mine.getConfig().getFillMode() == FillMode.MASK) {
|
||||
mine.getBlockSetter().fillAtLocations(mine.getConfig().getMaskPositions(), placed ->
|
||||
// Handle MASK fill mode
|
||||
if (mine.getConfig().blocks().fillMode() == FillMode.MASK) {
|
||||
List<Location> maskPositions = getMaskPositions();
|
||||
if (maskPositions.isEmpty()) {
|
||||
plugin.loLogger().warn("No mask positions found for mine " + mine.getName());
|
||||
running.set(false);
|
||||
return;
|
||||
}
|
||||
mine.getBlockSetter().fillAtLocations(maskPositions, placed ->
|
||||
Scheduler.get(plugin).run(() -> onResetComplete(placed, silent)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle CUBOID fill mode
|
||||
for (Cuboid region : regions) {
|
||||
mine.getBlockSetter().fill(region, blocksSet -> {
|
||||
totalBlocks.addAndGet(blocksSet);
|
||||
|
|
@ -90,6 +102,29 @@ public final class MineResetHandler {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets mask positions from config.
|
||||
*/
|
||||
private List<Location> getMaskPositions() {
|
||||
List<Location> positions = new ArrayList<>();
|
||||
var mask = mine.getConfig().blocks().mask();
|
||||
if (mask == null) {
|
||||
return positions;
|
||||
}
|
||||
|
||||
for (String posStr : mask.positions().keySet()) {
|
||||
try {
|
||||
Location loc = LocationParser.parse(posStr);
|
||||
if (loc != null) {
|
||||
positions.add(loc);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
plugin.loLogger().warn("Invalid mask position in mine " + mine.getName() + ": " + posStr);
|
||||
}
|
||||
}
|
||||
return positions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when all regions have been filled.
|
||||
* Executes post-reset actions in the main thread.
|
||||
|
|
@ -104,7 +139,7 @@ public final class MineResetHandler {
|
|||
broadcastReset();
|
||||
}
|
||||
|
||||
if (mine.getConfig().isTeleportOnReset()) {
|
||||
if (mine.getConfig().teleport().enabled()) {
|
||||
teleportPlayers();
|
||||
}
|
||||
|
||||
|
|
@ -115,7 +150,7 @@ public final class MineResetHandler {
|
|||
* Executes commands configured for reset.
|
||||
*/
|
||||
private void executeResetCommands() {
|
||||
List<String> commands = mine.getConfig().getResetCommands();
|
||||
List<String> commands = mine.getConfig().reset().commands();
|
||||
for (String command : commands) {
|
||||
String parsed = command.replace("%mine%", mine.getName());
|
||||
plugin.getServer().dispatchCommand(plugin.getServer().getConsoleSender(), parsed);
|
||||
|
|
@ -126,7 +161,7 @@ public final class MineResetHandler {
|
|||
* Broadcasts reset message to players.
|
||||
*/
|
||||
private void broadcastReset() {
|
||||
String message = mine.getConfig().getBroadcastReset();
|
||||
String message = mine.getConfig().reset().broadcastMessage();
|
||||
if (message != null && !message.isEmpty()) {
|
||||
String formatted = message.replace("%mine%", mine.getName());
|
||||
Component component = MiniMessage.miniMessage().deserialize(formatted);
|
||||
|
|
@ -138,8 +173,12 @@ public final class MineResetHandler {
|
|||
* Teleports players standing inside the mine to the configured location.
|
||||
*/
|
||||
private void teleportPlayers() {
|
||||
Location dest = mine.getConfig().getTeleportLocation();
|
||||
if (dest == null || dest.getWorld() == null) {
|
||||
var destOpt = mine.getConfig().teleport().getLocation();
|
||||
if (destOpt.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Location dest = destOpt.get();
|
||||
if (dest.getWorld() == null) {
|
||||
return;
|
||||
}
|
||||
for (Player p : dest.getWorld().getPlayers()) {
|
||||
|
|
|
|||
|
|
@ -2,27 +2,27 @@ package dev.loki.lomines.handler;
|
|||
|
||||
import dev.loki.lomines.LoMinesPlugin;
|
||||
import dev.loki.lomines.data.config.MineConfig;
|
||||
import dev.loki.lomines.data.reward.Reward;
|
||||
import dev.loki.lomines.data.config.block.BlockKey;
|
||||
import dev.loki.lomines.data.config.reward.RewardConfig;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Handles reward distribution for block breaks in mines.
|
||||
* Checks configured rewards and gives items/executes commands based on chance.
|
||||
* Updated for new configuration system (v2).
|
||||
*/
|
||||
public final class MineRewardHandler {
|
||||
|
||||
private final List<Reward> rewards;
|
||||
private final RewardConfig rewards;
|
||||
private final LoMinesPlugin plugin;
|
||||
private final Random random = new Random();
|
||||
|
||||
public MineRewardHandler(MineConfig config, LoMinesPlugin plugin) {
|
||||
this.rewards = config.getRewards();
|
||||
this.rewards = config.rewards();
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
|
|
@ -34,38 +34,39 @@ public final class MineRewardHandler {
|
|||
*/
|
||||
public void checkRewards(Player player, Block block) {
|
||||
Material material = block.getType();
|
||||
BlockKey key = new BlockKey.Vanilla(material);
|
||||
|
||||
for (Reward reward : rewards) {
|
||||
if (reward.matches(material) && rollChance(reward.chance())) {
|
||||
giveReward(player, reward);
|
||||
for (var entry : rewards.forBlock(key)) {
|
||||
if (entry.roll(random)) {
|
||||
giveReward(player, entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rolls a random chance check.
|
||||
*
|
||||
* @param chance The chance percentage (0-100)
|
||||
* @return true if the roll succeeds
|
||||
*/
|
||||
private boolean rollChance(double chance) {
|
||||
return random.nextDouble() * 100.0 < chance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives a reward to the player.
|
||||
*
|
||||
* @param player The player to give the reward to
|
||||
* @param reward The reward to give
|
||||
* @param reward The reward entry to give
|
||||
*/
|
||||
private void giveReward(Player player, Reward reward) {
|
||||
for (ItemStack item : reward.items()) {
|
||||
player.getInventory().addItem(item.clone());
|
||||
private void giveReward(Player player, RewardConfig.RewardEntry reward) {
|
||||
// Give items
|
||||
for (var item : reward.items()) {
|
||||
player.getInventory().addItem(item.toItemStack());
|
||||
}
|
||||
|
||||
// Execute commands
|
||||
for (String command : reward.commands()) {
|
||||
String parsed = command.replace("%player%", player.getName());
|
||||
String parsed = command
|
||||
.replace("%player%", player.getName())
|
||||
.replace("%uuid%", player.getUniqueId().toString());
|
||||
plugin.getServer().dispatchCommand(plugin.getServer().getConsoleSender(), parsed);
|
||||
}
|
||||
|
||||
// Handle vanilla drops prevention if configured
|
||||
if (reward.preventVanillaDrops()) {
|
||||
// Note: This would need to be handled in the block break event
|
||||
// by setting dropItems to false in the event
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,536 +0,0 @@
|
|||
package dev.loki.lomines.data;
|
||||
|
||||
import dev.loki.lomines.data.config.ConfigLoader;
|
||||
import dev.loki.lomines.data.config.MineConfig;
|
||||
import dev.loki.lomines.data.config.parser.ConfigParseException;
|
||||
import dev.loki.lomines.data.reward.Reward;
|
||||
import org.bukkit.*;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Unit tests for ConfigLoader.
|
||||
* Tests parsing of YAML configuration files into MineConfig objects.
|
||||
*/
|
||||
class ConfigLoaderTest {
|
||||
|
||||
@TempDir
|
||||
Path tempDir;
|
||||
private ConfigLoader configLoader;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
configLoader = new ConfigLoader();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoadValidConfig() throws Exception {
|
||||
// Create a valid YAML config file
|
||||
String yamlContent = """
|
||||
selection.1: world;0;64;0;0.0;0.0
|
||||
selection.2: world;10;74;10;0.0;0.0
|
||||
contents:
|
||||
stone: 50
|
||||
coal_ore: 30
|
||||
iron_ore: 20
|
||||
reset:
|
||||
ticks: 6000
|
||||
percent: 10.0
|
||||
reset-on-percent: false
|
||||
actionbar:
|
||||
enabled: true
|
||||
message: "Mine resetting soon"
|
||||
range: 50.0
|
||||
timer-format: "mm:ss"
|
||||
teleport-on-reset: false
|
||||
reset-commands:
|
||||
- "say Mine reset!"
|
||||
broadcast-reset: "Mine has been reset"
|
||||
random-rewards:
|
||||
- chance: 1.0
|
||||
prevent-drops: false
|
||||
blocks:
|
||||
- diamond_ore
|
||||
items:
|
||||
- type: diamond
|
||||
amount: 1
|
||||
commands:
|
||||
- "eco give %player% 100"
|
||||
""";
|
||||
|
||||
Path configFile = tempDir.resolve("test-mine.yml");
|
||||
Files.writeString(configFile, yamlContent);
|
||||
|
||||
// Mock Bukkit
|
||||
try (MockedStatic<Bukkit> bukkit = mockStatic(Bukkit.class)) {
|
||||
Server server = mock(Server.class);
|
||||
World world = mock(World.class);
|
||||
|
||||
bukkit.when(Bukkit::getServer).thenReturn(server);
|
||||
bukkit.when(() -> Bukkit.getWorld(anyString())).thenReturn(world);
|
||||
|
||||
when(world.getName()).thenReturn("world");
|
||||
|
||||
// Load the config
|
||||
MineConfig config = configLoader.load(configFile);
|
||||
|
||||
// Verify selections
|
||||
assertNotNull(config.getSelections());
|
||||
assertEquals(2, config.getSelections().size());
|
||||
|
||||
// Verify blocks
|
||||
assertNotNull(config.getBlocks());
|
||||
assertEquals(3, config.getBlocks().size());
|
||||
assertTrue(config.getBlocks().containsKey("stone"));
|
||||
assertTrue(config.getBlocks().containsKey("coal_ore"));
|
||||
assertTrue(config.getBlocks().containsKey("iron_ore"));
|
||||
assertEquals(50.0, config.getBlocks().get("stone"));
|
||||
assertEquals(30.0, config.getBlocks().get("coal_ore"));
|
||||
assertEquals(20.0, config.getBlocks().get("iron_ore"));
|
||||
|
||||
// Verify rewards
|
||||
assertNotNull(config.getRewards());
|
||||
assertEquals(1, config.getRewards().size());
|
||||
Reward reward = config.getRewards().get(0);
|
||||
assertEquals(1.0, reward.getChance());
|
||||
assertFalse(reward.isPreventDrops());
|
||||
assertEquals(1, reward.getMaterials().size());
|
||||
assertTrue(reward.getMaterials().contains(Material.DIAMOND_ORE));
|
||||
|
||||
// Verify reset settings
|
||||
assertEquals(6000, config.getResetTicks());
|
||||
assertEquals(10.0, config.getResetPercent());
|
||||
assertFalse(config.isResetOnPercentEnabled());
|
||||
|
||||
// Verify action bar settings
|
||||
assertTrue(config.isActionBarEnabled());
|
||||
assertEquals("Mine resetting soon", config.getActionBarMessage());
|
||||
assertEquals(50.0, config.getActionBarRange());
|
||||
|
||||
// Verify other settings
|
||||
assertEquals("mm:ss", config.getTimerFormat());
|
||||
assertFalse(config.isTeleportOnReset());
|
||||
assertNull(config.getTeleportLocation());
|
||||
assertEquals(1, config.getResetCommands().size());
|
||||
assertEquals("say Mine reset!", config.getResetCommands().get(0));
|
||||
assertEquals("Mine has been reset", config.getBroadcastReset());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoadMissingFile() {
|
||||
Path nonExistentFile = tempDir.resolve("nonexistent.yml");
|
||||
|
||||
assertThrows(IOException.class, () -> {
|
||||
configLoader.load(nonExistentFile);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoadNullPath() {
|
||||
ConfigParseException exception = assertThrows(ConfigParseException.class, () -> {
|
||||
configLoader.load(null);
|
||||
});
|
||||
|
||||
assertTrue(exception.getMessage().contains("cannot be null"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoadMissingSelections() throws Exception {
|
||||
String yamlContent = """
|
||||
contents:
|
||||
stone: 100
|
||||
""";
|
||||
|
||||
Path configFile = tempDir.resolve("test-mine.yml");
|
||||
Files.writeString(configFile, yamlContent);
|
||||
|
||||
ConfigParseException exception = assertThrows(ConfigParseException.class, () -> {
|
||||
configLoader.load(configFile);
|
||||
});
|
||||
|
||||
assertTrue(exception.getMessage().contains("No selection coordinates found"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoadOddNumberOfSelections() throws Exception {
|
||||
String yamlContent = """
|
||||
selection.1: world;0;64;0;0.0;0.0
|
||||
selection.2: world;10;74;10;0.0;0.0
|
||||
selection.3: world;20;64;20;0.0;0.0
|
||||
contents:
|
||||
stone: 100
|
||||
""";
|
||||
|
||||
Path configFile = tempDir.resolve("test-mine.yml");
|
||||
Files.writeString(configFile, yamlContent);
|
||||
|
||||
try (MockedStatic<Bukkit> bukkit = mockStatic(Bukkit.class)) {
|
||||
Server server = mock(Server.class);
|
||||
World world = mock(World.class);
|
||||
|
||||
bukkit.when(Bukkit::getServer).thenReturn(server);
|
||||
bukkit.when(() -> Bukkit.getWorld(anyString())).thenReturn(world);
|
||||
|
||||
when(world.getName()).thenReturn("world");
|
||||
|
||||
ConfigParseException exception = assertThrows(ConfigParseException.class, () -> {
|
||||
configLoader.load(configFile);
|
||||
});
|
||||
|
||||
assertTrue(exception.getMessage().contains("must be in pairs"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoadMissingContents() throws Exception {
|
||||
String yamlContent = """
|
||||
selection.1: world;0;64;0;0.0;0.0
|
||||
selection.2: world;10;74;10;0.0;0.0
|
||||
""";
|
||||
|
||||
Path configFile = tempDir.resolve("test-mine.yml");
|
||||
Files.writeString(configFile, yamlContent);
|
||||
|
||||
try (MockedStatic<Bukkit> bukkit = mockStatic(Bukkit.class)) {
|
||||
Server server = mock(Server.class);
|
||||
World world = mock(World.class);
|
||||
|
||||
bukkit.when(Bukkit::getServer).thenReturn(server);
|
||||
bukkit.when(() -> Bukkit.getWorld(anyString())).thenReturn(world);
|
||||
|
||||
when(world.getName()).thenReturn("world");
|
||||
|
||||
ConfigParseException exception = assertThrows(ConfigParseException.class, () -> {
|
||||
configLoader.load(configFile);
|
||||
});
|
||||
|
||||
assertTrue(exception.getMessage().contains("Missing required 'contents' section"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoadInvalidMaterial() throws Exception {
|
||||
String yamlContent = """
|
||||
selection.1: world;0;64;0;0.0;0.0
|
||||
selection.2: world;10;74;10;0.0;0.0
|
||||
contents:
|
||||
invalid_material: 100
|
||||
""";
|
||||
|
||||
Path configFile = tempDir.resolve("test-mine.yml");
|
||||
Files.writeString(configFile, yamlContent);
|
||||
|
||||
try (MockedStatic<Bukkit> bukkit = mockStatic(Bukkit.class)) {
|
||||
Server server = mock(Server.class);
|
||||
World world = mock(World.class);
|
||||
|
||||
bukkit.when(Bukkit::getServer).thenReturn(server);
|
||||
bukkit.when(() -> Bukkit.getWorld(anyString())).thenReturn(world);
|
||||
|
||||
when(world.getName()).thenReturn("world");
|
||||
|
||||
ConfigParseException exception = assertThrows(ConfigParseException.class, () -> {
|
||||
configLoader.load(configFile);
|
||||
});
|
||||
|
||||
assertTrue(exception.getMessage().contains("Unknown material"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoadInvalidBlockWeight() throws Exception {
|
||||
String yamlContent = """
|
||||
selection.1: world;0;64;0;0.0;0.0
|
||||
selection.2: world;10;74;10;0.0;0.0
|
||||
contents:
|
||||
stone: -10
|
||||
""";
|
||||
|
||||
Path configFile = tempDir.resolve("test-mine.yml");
|
||||
Files.writeString(configFile, yamlContent);
|
||||
|
||||
try (MockedStatic<Bukkit> bukkit = mockStatic(Bukkit.class)) {
|
||||
Server server = mock(Server.class);
|
||||
World world = mock(World.class);
|
||||
|
||||
bukkit.when(Bukkit::getServer).thenReturn(server);
|
||||
bukkit.when(() -> Bukkit.getWorld(anyString())).thenReturn(world);
|
||||
|
||||
when(world.getName()).thenReturn("world");
|
||||
|
||||
ConfigParseException exception = assertThrows(ConfigParseException.class, () -> {
|
||||
configLoader.load(configFile);
|
||||
});
|
||||
|
||||
assertTrue(exception.getMessage().contains("must be positive"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoadInvalidRewardChance() throws Exception {
|
||||
String yamlContent = """
|
||||
selection.1: world;0;64;0;0.0;0.0
|
||||
selection.2: world;10;74;10;0.0;0.0
|
||||
contents:
|
||||
stone: 100
|
||||
random-rewards:
|
||||
- chance: 150
|
||||
blocks:
|
||||
- diamond_ore
|
||||
""";
|
||||
|
||||
Path configFile = tempDir.resolve("test-mine.yml");
|
||||
Files.writeString(configFile, yamlContent);
|
||||
|
||||
try (MockedStatic<Bukkit> bukkit = mockStatic(Bukkit.class)) {
|
||||
Server server = mock(Server.class);
|
||||
World world = mock(World.class);
|
||||
|
||||
bukkit.when(Bukkit::getServer).thenReturn(server);
|
||||
bukkit.when(() -> Bukkit.getWorld(anyString())).thenReturn(world);
|
||||
|
||||
when(world.getName()).thenReturn("world");
|
||||
|
||||
ConfigParseException exception = assertThrows(ConfigParseException.class, () -> {
|
||||
configLoader.load(configFile);
|
||||
});
|
||||
|
||||
assertTrue(exception.getMessage().contains("between 0 and 100"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoadWithTeleportLocation() throws Exception {
|
||||
String yamlContent = """
|
||||
selection.1: world;0;64;0;0.0;0.0
|
||||
selection.2: world;10;74;10;0.0;0.0
|
||||
contents:
|
||||
stone: 100
|
||||
teleport-on-reset: true
|
||||
teleport-location: world;5;70;5;90.0;0.0
|
||||
""";
|
||||
|
||||
Path configFile = tempDir.resolve("test-mine.yml");
|
||||
Files.writeString(configFile, yamlContent);
|
||||
|
||||
try (MockedStatic<Bukkit> bukkit = mockStatic(Bukkit.class)) {
|
||||
Server server = mock(Server.class);
|
||||
World world = mock(World.class);
|
||||
|
||||
bukkit.when(Bukkit::getServer).thenReturn(server);
|
||||
bukkit.when(() -> Bukkit.getWorld(anyString())).thenReturn(world);
|
||||
|
||||
when(world.getName()).thenReturn("world");
|
||||
|
||||
MineConfig config = configLoader.load(configFile);
|
||||
|
||||
assertTrue(config.isTeleportOnReset());
|
||||
assertNotNull(config.getTeleportLocation());
|
||||
assertEquals(5.0, config.getTeleportLocation().getX());
|
||||
assertEquals(70.0, config.getTeleportLocation().getY());
|
||||
assertEquals(5.0, config.getTeleportLocation().getZ());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testSaveAndLoadRoundTrip() throws Exception {
|
||||
// Create a config file to load (without items to avoid ItemStack initialization issues)
|
||||
String yamlContent = """
|
||||
selection.1: world;0;64;0;0.0;0.0
|
||||
selection.2: world;10;74;10;0.0;0.0
|
||||
contents:
|
||||
stone: 50
|
||||
coal_ore: 30
|
||||
reset:
|
||||
ticks: 6000
|
||||
percent: 10.0
|
||||
reset-on-percent: false
|
||||
actionbar:
|
||||
enabled: true
|
||||
message: "Mine resetting soon"
|
||||
range: 50.0
|
||||
timer-format: "mm:ss"
|
||||
teleport-on-reset: false
|
||||
reset-commands:
|
||||
- "say Mine reset!"
|
||||
broadcast-reset: "Mine has been reset"
|
||||
random-rewards:
|
||||
- chance: 1.0
|
||||
prevent-drops: false
|
||||
blocks:
|
||||
- diamond_ore
|
||||
commands:
|
||||
- "eco give %player% 100"
|
||||
""";
|
||||
|
||||
Path configFile = tempDir.resolve("test-mine.yml");
|
||||
Files.writeString(configFile, yamlContent);
|
||||
|
||||
// Mock Bukkit
|
||||
try (MockedStatic<Bukkit> bukkit = mockStatic(Bukkit.class)) {
|
||||
Server server = mock(Server.class);
|
||||
World world = mock(World.class);
|
||||
|
||||
bukkit.when(Bukkit::getServer).thenReturn(server);
|
||||
bukkit.when(() -> Bukkit.getWorld(anyString())).thenReturn(world);
|
||||
|
||||
when(world.getName()).thenReturn("world");
|
||||
|
||||
// Load the config
|
||||
MineConfig config = configLoader.load(configFile);
|
||||
|
||||
// Save to a new file
|
||||
Path savedFile = tempDir.resolve("saved-mine.yml");
|
||||
configLoader.save(config, savedFile);
|
||||
|
||||
// Verify the file was created
|
||||
assertTrue(Files.exists(savedFile));
|
||||
|
||||
// Verify the content is valid YAML
|
||||
String savedContent = Files.readString(savedFile);
|
||||
assertFalse(savedContent.isEmpty());
|
||||
|
||||
// Load the saved config
|
||||
MineConfig loadedConfig = configLoader.load(savedFile);
|
||||
|
||||
// Verify key properties match
|
||||
assertEquals(config.getResetTicks(), loadedConfig.getResetTicks());
|
||||
assertEquals(config.getResetPercent(), loadedConfig.getResetPercent());
|
||||
assertEquals(config.isResetOnPercentEnabled(), loadedConfig.isResetOnPercentEnabled());
|
||||
assertEquals(config.isActionBarEnabled(), loadedConfig.isActionBarEnabled());
|
||||
assertEquals(config.getActionBarMessage(), loadedConfig.getActionBarMessage());
|
||||
assertEquals(config.getActionBarRange(), loadedConfig.getActionBarRange());
|
||||
assertEquals(config.getTimerFormat(), loadedConfig.getTimerFormat());
|
||||
assertEquals(config.isTeleportOnReset(), loadedConfig.isTeleportOnReset());
|
||||
assertEquals(config.getBroadcastReset(), loadedConfig.getBroadcastReset());
|
||||
assertEquals(config.getResetCommands().size(), loadedConfig.getResetCommands().size());
|
||||
assertEquals(config.getRewards().size(), loadedConfig.getRewards().size());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSaveNullConfig() {
|
||||
Path configFile = tempDir.resolve("test.yml");
|
||||
|
||||
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
|
||||
configLoader.save(null, configFile);
|
||||
});
|
||||
|
||||
assertTrue(exception.getMessage().contains("Config cannot be null"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSaveNullPath() throws Exception {
|
||||
// Create a minimal config
|
||||
try (MockedStatic<Bukkit> bukkit = mockStatic(Bukkit.class)) {
|
||||
Server server = mock(Server.class);
|
||||
World world = mock(World.class);
|
||||
|
||||
bukkit.when(Bukkit::getServer).thenReturn(server);
|
||||
bukkit.when(() -> Bukkit.getWorld(anyString())).thenReturn(world);
|
||||
|
||||
when(world.getName()).thenReturn("world");
|
||||
|
||||
Location loc1 = new Location(world, 0, 64, 0, 0, 0);
|
||||
Location loc2 = new Location(world, 10, 74, 10, 0, 0);
|
||||
|
||||
MineConfig config = MineConfig.builder()
|
||||
.selections(List.of(loc1, loc2))
|
||||
.blocks(Map.of("stone", 100.0))
|
||||
.rewards(List.of())
|
||||
.build();
|
||||
|
||||
IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> {
|
||||
configLoader.save(config, null);
|
||||
});
|
||||
|
||||
assertTrue(exception.getMessage().contains("Config file path cannot be null"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSaveWithTeleportLocation() throws Exception {
|
||||
try (MockedStatic<Bukkit> bukkit = mockStatic(Bukkit.class)) {
|
||||
Server server = mock(Server.class);
|
||||
World world = mock(World.class);
|
||||
|
||||
bukkit.when(Bukkit::getServer).thenReturn(server);
|
||||
bukkit.when(() -> Bukkit.getWorld(anyString())).thenReturn(world);
|
||||
|
||||
when(world.getName()).thenReturn("world");
|
||||
|
||||
Location loc1 = new Location(world, 0, 64, 0, 0, 0);
|
||||
Location loc2 = new Location(world, 10, 74, 10, 0, 0);
|
||||
Location teleportLoc = new Location(world, 5, 70, 5, 90, 0);
|
||||
|
||||
MineConfig config = MineConfig.builder()
|
||||
.selections(List.of(loc1, loc2))
|
||||
.blocks(Map.of("stone", 100.0))
|
||||
.rewards(List.of())
|
||||
.teleportOnReset(true)
|
||||
.teleportLocation(teleportLoc)
|
||||
.build();
|
||||
|
||||
Path savedFile = tempDir.resolve("with-teleport.yml");
|
||||
configLoader.save(config, savedFile);
|
||||
|
||||
// Verify the file was created
|
||||
assertTrue(Files.exists(savedFile));
|
||||
|
||||
// Load and verify
|
||||
MineConfig loadedConfig = configLoader.load(savedFile);
|
||||
assertTrue(loadedConfig.isTeleportOnReset());
|
||||
assertNotNull(loadedConfig.getTeleportLocation());
|
||||
assertEquals(5.0, loadedConfig.getTeleportLocation().getX());
|
||||
assertEquals(70.0, loadedConfig.getTeleportLocation().getY());
|
||||
assertEquals(5.0, loadedConfig.getTeleportLocation().getZ());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSaveEmptyRewards() throws Exception {
|
||||
try (MockedStatic<Bukkit> bukkit = mockStatic(Bukkit.class)) {
|
||||
Server server = mock(Server.class);
|
||||
World world = mock(World.class);
|
||||
|
||||
bukkit.when(Bukkit::getServer).thenReturn(server);
|
||||
bukkit.when(() -> Bukkit.getWorld(anyString())).thenReturn(world);
|
||||
|
||||
when(world.getName()).thenReturn("world");
|
||||
|
||||
Location loc1 = new Location(world, 0, 64, 0, 0, 0);
|
||||
Location loc2 = new Location(world, 10, 74, 10, 0, 0);
|
||||
|
||||
MineConfig config = MineConfig.builder()
|
||||
.selections(List.of(loc1, loc2))
|
||||
.blocks(Map.of("stone", 100.0))
|
||||
.rewards(List.of())
|
||||
.build();
|
||||
|
||||
Path savedFile = tempDir.resolve("no-rewards.yml");
|
||||
configLoader.save(config, savedFile);
|
||||
|
||||
// Verify the file was created
|
||||
assertTrue(Files.exists(savedFile));
|
||||
|
||||
// Load and verify
|
||||
MineConfig loadedConfig = configLoader.load(savedFile);
|
||||
assertTrue(loadedConfig.getRewards().isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue