chore: rewrite ARCHITECTURE, enable tests via MockBukkit, checkstyle for tests, cleanup bin/libs

This commit is contained in:
loki5512344 2026-08-12 13:59:33 +02:00
parent a0c1ab8ca1
commit e8d516020c
Signed by: boba
GPG key ID: 253067914055423B
53 changed files with 1093 additions and 1410 deletions

File diff suppressed because it is too large Load diff

View file

@ -41,9 +41,13 @@ dependencies {
testImplementation("io.kotest:kotest-property:5.9.1") testImplementation("io.kotest:kotest-property:5.9.1")
testImplementation("org.mockito:mockito-core:5.14.2") testImplementation("org.mockito:mockito-core:5.14.2")
testImplementation("org.mockito:mockito-inline:5.2.0") testImplementation("org.mockito:mockito-inline:5.2.0")
testImplementation("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT") testImplementation("org.mockbukkit.mockbukkit:mockbukkit-v1.21:4.110.0") {
exclude(group = "org.junit.jupiter", module = "junit-jupiter-api")
}
testImplementation("io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT")
testImplementation("net.kyori:adventure-api:4.17.0") testImplementation("net.kyori:adventure-api:4.17.0")
testImplementation("net.kyori:adventure-text-minimessage:4.17.0") testImplementation("net.kyori:adventure-text-minimessage:4.17.0")
testImplementation("com.sk89q.worldguard:worldguard-bukkit:7.0.13")
} }
checkstyle { checkstyle {

View file

@ -8,10 +8,6 @@
<property name="severity" value="error"/> <property name="severity" value="error"/>
<property name="fileExtensions" value="java"/> <property name="fileExtensions" value="java"/>
<module name="BeforeExecutionExclusionFileFilter">
<property name="fileNamePattern" value=".*[\\/]src[\\/]test[\\/].*"/>
</module>
<module name="SuppressWarningsFilter"/> <module name="SuppressWarningsFilter"/>
<module name="LineLength"> <module name="LineLength">
@ -24,7 +20,7 @@
<module name="SuppressWarningsHolder"/> <module name="SuppressWarningsHolder"/>
<module name="MethodLength"> <module name="MethodLength">
<property name="max" value="80"/> <property name="max" value="30"/>
<property name="countEmpty" value="false"/> <property name="countEmpty" value="false"/>
</module> </module>

View file

View file

@ -3,6 +3,7 @@ package dev.loki.lomines.command.admin.info;
import dev.loki.lomines.LoMinesPlugin; import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.core.mine.model.Mine; import dev.loki.lomines.core.mine.model.Mine;
import dev.loki.lomines.data.config.block.BlockKey; import dev.loki.lomines.data.config.block.BlockKey;
import dev.loki.lomines.data.config.model.MineConfig;
import dev.loki.lomines.util.location.geo.Cuboid; import dev.loki.lomines.util.location.geo.Cuboid;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
@ -40,8 +41,16 @@ public class InfoCommand {
sender.sendMessage(Component.text("§7Блоков: §f" + mine.getBlocks() + "§7/§f" + mine.getTotalVolume() + sender.sendMessage(Component.text("§7Блоков: §f" + mine.getBlocks() + "§7/§f" + mine.getTotalVolume() +
" §7(§f" + String.format("%.1f", mine.getPercentFilled()) + "%§7)")); " §7(§f" + String.format("%.1f", mine.getPercentFilled()) + "%§7)"));
printRegions(sender, mine.getRegions());
printBlocks(sender, config.blocks().weights());
printResetAndTeleport(sender, config);
sender.sendMessage(Component.text("§6§lНаграды: §f" + config.rewards().entries().size() + " записей"));
sender.sendMessage(Component.text("§8§m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
}
private void printRegions(CommandSender sender, List<Cuboid> regions) {
sender.sendMessage(Component.text("§6§lРегионы:")); sender.sendMessage(Component.text("§6§lРегионы:"));
List<Cuboid> regions = mine.getRegions();
for (int i = 0; i < regions.size(); i++) { for (int i = 0; i < regions.size(); i++) {
Cuboid r = regions.get(i); Cuboid r = regions.get(i);
sender.sendMessage(Component.text("§7 #" + (i + 1) + ": §f" + sender.sendMessage(Component.text("§7 #" + (i + 1) + ": §f" +
@ -50,8 +59,10 @@ public class InfoCommand {
" §7(§f" + r.getVolume() + " §7блоков)")); " §7(§f" + r.getVolume() + " §7блоков)"));
} }
sender.sendMessage(Component.text("")); sender.sendMessage(Component.text(""));
}
private void printBlocks(CommandSender sender, Map<BlockKey, Double> weights) {
sender.sendMessage(Component.text("§6§lБлоки:")); sender.sendMessage(Component.text("§6§lБлоки:"));
Map<BlockKey, Double> weights = config.blocks().weights();
weights.entrySet().stream() weights.entrySet().stream()
.sorted((a, b) -> Double.compare(b.getValue(), a.getValue())) .sorted((a, b) -> Double.compare(b.getValue(), a.getValue()))
.limit(10) .limit(10)
@ -60,7 +71,9 @@ public class InfoCommand {
if (weights.size() > 10) { if (weights.size() > 10) {
sender.sendMessage(Component.text("§7 ... и ещё " + (weights.size() - 10) + " блоков")); sender.sendMessage(Component.text("§7 ... и ещё " + (weights.size() - 10) + " блоков"));
} }
}
private void printResetAndTeleport(CommandSender sender, MineConfig config) {
sender.sendMessage(Component.text("§6§lНастройки сброса:")); sender.sendMessage(Component.text("§6§lНастройки сброса:"));
sender.sendMessage(Component.text("§7 Интервал: §f" + config.reset().intervalDisplay())); sender.sendMessage(Component.text("§7 Интервал: §f" + config.reset().intervalDisplay()));
sender.sendMessage(Component.text("§7 Триггер по %: §f" + sender.sendMessage(Component.text("§7 Триггер по %: §f" +
@ -71,9 +84,6 @@ public class InfoCommand {
(config.teleport().enabled() ? "§aустановлена" : "§7не задана"))); (config.teleport().enabled() ? "§aустановлена" : "§7не задана")));
sender.sendMessage(Component.text("§7 Точка спавна: §f" + sender.sendMessage(Component.text("§7 Точка спавна: §f" +
(config.playerSpawn().enabled() ? "§aустановлена" : "§7используется точка телепорта"))); (config.playerSpawn().enabled() ? "§aустановлена" : "§7используется точка телепорта")));
sender.sendMessage(Component.text("§6§lНаграды: §f" + config.rewards().entries().size() + " записей"));
sender.sendMessage(Component.text("§8§m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
} }
private String formatBlockKey(BlockKey key) { private String formatBlockKey(BlockKey key) {

View file

@ -33,6 +33,20 @@ public final class RegionActionHandler {
return; return;
} }
Cuboid newRegion = resolveNewRegion(player, mine);
if (newRegion == null) {
return;
}
List<Cuboid> currentRegions = new ArrayList<>(mine.getRegions());
currentRegions.add(newRegion);
updateMineRegions(mine, new RegionConfig(currentRegions));
sendRegionAdded(player, newRegion, currentRegions.size());
}
private Cuboid resolveNewRegion(Player player, Mine mine) {
var wandManager = plugin.getGroupWandManager(); var wandManager = plugin.getGroupWandManager();
var session = wandManager.getSession(player.getUniqueId()); var session = wandManager.getSession(player.getUniqueId());
int slot = session.getActiveSlot(); int slot = session.getActiveSlot();
@ -40,7 +54,7 @@ public final class RegionActionHandler {
var pos2 = session.getPos2(slot); var pos2 = session.getPos2(slot);
if (pos1 == null || pos2 == null) { if (pos1 == null || pos2 == null) {
player.sendMessage(Component.text("§cВыделите регион палочкой! ЛКМ - 1-я точка, ПКМ - 2-я точка")); player.sendMessage(Component.text("§cВыделите регион палочкой! ЛКМ - 1-я точка, ПКМ - 2-я точка"));
return; return null;
} }
Cuboid newRegion = new Cuboid(pos1, pos2); Cuboid newRegion = new Cuboid(pos1, pos2);
String mineWorld = mine.getConfig().region().worldName(); String mineWorld = mine.getConfig().region().worldName();
@ -48,21 +62,17 @@ public final class RegionActionHandler {
if (!mineWorld.equals(regionWorld)) { if (!mineWorld.equals(regionWorld)) {
player.sendMessage(Component.text("§cРегион должен быть в мире §f" + mineWorld + "§c, а не §f" + regionWorld)); player.sendMessage(Component.text("§cРегион должен быть в мире §f" + mineWorld + "§c, а не §f" + regionWorld));
return; return null;
}
session.clearCorners(slot);
return newRegion;
} }
List<Cuboid> currentRegions = new ArrayList<>(mine.getRegions()); private void sendRegionAdded(Player player, Cuboid newRegion, int count) {
currentRegions.add(newRegion); player.sendMessage(Component.text("§aДобавлен регион #" + count));
var newRegionConfig = new RegionConfig(currentRegions);
updateMineRegions(mine, newRegionConfig);
player.sendMessage(Component.text("§aДобавлен регион #" + currentRegions.size()));
player.sendMessage(Component.text("§7От: §f" + newRegion.getMinX() + ", " + newRegion.getMinY() + ", " + newRegion.getMinZ())); player.sendMessage(Component.text("§7От: §f" + newRegion.getMinX() + ", " + newRegion.getMinY() + ", " + newRegion.getMinZ()));
player.sendMessage(Component.text("§7До: §f" + newRegion.getMaxX() + ", " + newRegion.getMaxY() + ", " + newRegion.getMaxZ())); player.sendMessage(Component.text("§7До: §f" + newRegion.getMaxX() + ", " + newRegion.getMaxY() + ", " + newRegion.getMaxZ()));
player.sendMessage(Component.text("§7Объём: §f" + newRegion.getVolume() + " §7блоков")); player.sendMessage(Component.text("§7Объём: §f" + newRegion.getVolume() + " §7блоков"));
session.clearCorners(slot);
} }
public void removeRegion(CommandSender sender, String mineName, int index) { public void removeRegion(CommandSender sender, String mineName, int index) {

View file

@ -21,7 +21,14 @@ public class RegionCommands {
public void handle(CommandSender sender, String subcommand, String[] args) { public void handle(CommandSender sender, String subcommand, String[] args) {
switch (subcommand.toLowerCase()) { switch (subcommand.toLowerCase()) {
case "regions" -> { case "regions" -> handleRegions(sender, args);
case "addregion" -> handleAddRegion(sender, args);
case "removeregion" -> handleRemoveRegion(sender, args);
default -> {}
}
}
private void handleRegions(CommandSender sender, String[] args) {
if (args.length < 1) { if (args.length < 1) {
sender.sendMessage(Component.text("Usage: /lm regions <mine>", NamedTextColor.RED)); sender.sendMessage(Component.text("Usage: /lm regions <mine>", NamedTextColor.RED));
return; return;
@ -48,14 +55,16 @@ public class RegionCommands {
} }
sender.sendMessage(Component.text("§8§m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")); sender.sendMessage(Component.text("§8§m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
} }
case "addregion" -> {
private void handleAddRegion(CommandSender sender, String[] args) {
if (args.length < 1) { if (args.length < 1) {
sender.sendMessage(Component.text("Usage: /lm addregion <mine>", NamedTextColor.RED)); sender.sendMessage(Component.text("Usage: /lm addregion <mine>", NamedTextColor.RED));
return; return;
} }
actionHandler.addRegion(sender, args[0]); actionHandler.addRegion(sender, args[0]);
} }
case "removeregion" -> {
private void handleRemoveRegion(CommandSender sender, String[] args) {
if (args.length < 2) { if (args.length < 2) {
sender.sendMessage(Component.text("Usage: /lm removeregion <mine> <index>", NamedTextColor.RED)); sender.sendMessage(Component.text("Usage: /lm removeregion <mine> <index>", NamedTextColor.RED));
return; return;
@ -67,7 +76,4 @@ public class RegionCommands {
sender.sendMessage(Component.text("Индекс должен быть числом: " + args[1], NamedTextColor.RED)); sender.sendMessage(Component.text("Индекс должен быть числом: " + args[1], NamedTextColor.RED));
} }
} }
default -> {}
}
}
} }

View file

@ -25,28 +25,41 @@ public class AdminCommands {
public void handle(CommandSender sender, String subcommand, String[] args) { public void handle(CommandSender sender, String subcommand, String[] args) {
switch (subcommand.toLowerCase()) { switch (subcommand.toLowerCase()) {
case "create" -> { case "create" -> handleCreate(sender, args);
case "delete" -> handleDelete(sender, args);
case "edit" -> handleEdit(sender, args);
case "reset" -> handleReset(sender, args);
case "reload" -> actionHandler.reload(sender);
case "list" -> handleList(sender);
default -> {}
}
}
private void handleCreate(CommandSender sender, String[] args) {
if (args.length < 1) { if (args.length < 1) {
sender.sendMessage(Component.text("Usage: /lm create <name>")); sender.sendMessage(Component.text("Usage: /lm create <name>"));
return; return;
} }
actionHandler.create(sender, args[0]); actionHandler.create(sender, args[0]);
} }
case "delete" -> {
private void handleDelete(CommandSender sender, String[] args) {
if (args.length < 1) { if (args.length < 1) {
sender.sendMessage(Component.text("Usage: /lm delete <name>")); sender.sendMessage(Component.text("Usage: /lm delete <name>"));
return; return;
} }
actionHandler.delete(sender, args[0]); actionHandler.delete(sender, args[0]);
} }
case "edit" -> {
private void handleEdit(CommandSender sender, String[] args) {
if (args.length < 1) { if (args.length < 1) {
sender.sendMessage(Component.text("Usage: /lm edit <mine>")); sender.sendMessage(Component.text("Usage: /lm edit <mine>"));
return; return;
} }
actionHandler.edit(sender, args[0]); actionHandler.edit(sender, args[0]);
} }
case "reset" -> {
private void handleReset(CommandSender sender, String[] args) {
if (args.length < 1) { if (args.length < 1) {
sender.sendMessage(Component.text("Usage: /lm reset <mine> [silent]")); sender.sendMessage(Component.text("Usage: /lm reset <mine> [silent]"));
return; return;
@ -54,8 +67,8 @@ public class AdminCommands {
boolean silent = args.length > 1 && (args[1].equalsIgnoreCase("true") || args[1].equalsIgnoreCase("silent")); boolean silent = args.length > 1 && (args[1].equalsIgnoreCase("true") || args[1].equalsIgnoreCase("silent"));
actionHandler.reset(sender, args[0], silent); actionHandler.reset(sender, args[0], silent);
} }
case "reload" -> actionHandler.reload(sender);
case "list" -> { private void handleList(CommandSender sender) {
Collection<Mine> allMines = mines.getAll(); Collection<Mine> allMines = mines.getAll();
if (allMines.isEmpty()) { if (allMines.isEmpty()) {
sender.sendMessage(Component.text("No mines found")); sender.sendMessage(Component.text("No mines found"));
@ -67,9 +80,6 @@ public class AdminCommands {
sender.sendMessage(Component.text("- " + mine.getName() + " " + status)); sender.sendMessage(Component.text("- " + mine.getName() + " " + status));
} }
} }
default -> {}
}
}
private String formatMineStatus(Mine mine) { private String formatMineStatus(Mine mine) {
int blocks = mine.getBlocks(); int blocks = mine.getBlocks();

View file

@ -23,27 +23,51 @@ public class CopyCommand {
String fromMineName = args[0]; String fromMineName = args[0];
String toMineName = args[1]; String toMineName = args[1];
MinePair pair = resolveMines(sender, fromMineName, toMineName);
if (pair == null) {
return;
}
var sourceConfig = pair.fromMine().getConfig();
MineConfig newTargetConfig = buildCopiedConfig(pair.toMine(), sourceConfig);
try {
plugin.getMines().updateMineConfig(pair.toMine().getName(), newTargetConfig);
} catch (Exception e) {
sender.sendMessage(Component.text("Ошибка сохранения: " + e.getMessage(), NamedTextColor.RED));
return;
}
sendCopySummary(sender, pair.fromMine(), pair.toMine(), sourceConfig);
}
private record MinePair(Mine fromMine, Mine toMine) {
}
private MinePair resolveMines(CommandSender sender, String fromMineName, String toMineName) {
Mine fromMine = plugin.getMines().find(fromMineName).orElse(null); Mine fromMine = plugin.getMines().find(fromMineName).orElse(null);
if (fromMine == null) { if (fromMine == null) {
sender.sendMessage(Component.text("Исходная шахта не найдена: " + fromMineName, NamedTextColor.RED)); sender.sendMessage(Component.text("Исходная шахта не найдена: " + fromMineName, NamedTextColor.RED));
return; return null;
} }
Mine toMine = plugin.getMines().find(toMineName).orElse(null); Mine toMine = plugin.getMines().find(toMineName).orElse(null);
if (toMine == null) { if (toMine == null) {
sender.sendMessage(Component.text("Целевая шахта не найдена: " + toMineName, NamedTextColor.RED)); sender.sendMessage(Component.text("Целевая шахта не найдена: " + toMineName, NamedTextColor.RED));
return; return null;
} }
if (fromMine.getName().equalsIgnoreCase(toMine.getName())) { if (fromMine.getName().equalsIgnoreCase(toMine.getName())) {
sender.sendMessage(Component.text("Нельзя копировать шахту саму в себя!", NamedTextColor.RED)); sender.sendMessage(Component.text("Нельзя копировать шахту саму в себя!", NamedTextColor.RED));
return; return null;
}
return new MinePair(fromMine, toMine);
} }
var sourceConfig = fromMine.getConfig(); private MineConfig buildCopiedConfig(Mine toMine, MineConfig sourceConfig) {
var targetConfig = toMine.getConfig(); var targetConfig = toMine.getConfig();
return MineConfig.builder(toMine.getName())
MineConfig newTargetConfig = MineConfig.builder(toMine.getName())
.region(targetConfig.region()) .region(targetConfig.region())
.blocks(sourceConfig.blocks()) .blocks(sourceConfig.blocks())
.reset(sourceConfig.reset()) .reset(sourceConfig.reset())
@ -53,14 +77,9 @@ public class CopyCommand {
.worldGuard(sourceConfig.worldGuard()) .worldGuard(sourceConfig.worldGuard())
.playerSpawn(sourceConfig.playerSpawn()) .playerSpawn(sourceConfig.playerSpawn())
.build(); .build();
try {
plugin.getMines().updateMineConfig(toMine.getName(), newTargetConfig);
} catch (Exception e) {
sender.sendMessage(Component.text("Ошибка сохранения: " + e.getMessage(), NamedTextColor.RED));
return;
} }
private void sendCopySummary(CommandSender sender, Mine fromMine, Mine toMine, MineConfig sourceConfig) {
sender.sendMessage(Component.text("§aКонфигурация скопирована из §f" + fromMine.getName() + sender.sendMessage(Component.text("§aКонфигурация скопирована из §f" + fromMine.getName() +
" §aв §f" + toMine.getName())); " §aв §f" + toMine.getName()));
sender.sendMessage(Component.text("§7Скопировано:")); sender.sendMessage(Component.text("§7Скопировано:"));

View file

@ -40,18 +40,7 @@ public final class TeleportActionHandler {
} }
var config = mine.getConfig(); var config = mine.getConfig();
var newTeleport = TeleportConfig.at(player.getLocation()); var updatedConfig = withTeleport(config, TeleportConfig.at(player.getLocation()));
var updatedConfig = MineConfig.builder(mineName)
.region(config.region())
.blocks(config.blocks())
.reset(config.reset())
.rewards(config.rewards())
.teleport(newTeleport)
.ui(config.ui())
.worldGuard(config.worldGuard())
.playerSpawn(config.playerSpawn())
.build();
plugin.getMines().getFileManager().saveConfig(updatedConfig); plugin.getMines().getFileManager().saveConfig(updatedConfig);
sender.sendMessage(MessageFormatter.success("Teleport location for mine '" + mineName + "' set to your current position")); sender.sendMessage(MessageFormatter.success("Teleport location for mine '" + mineName + "' set to your current position"));
@ -76,18 +65,7 @@ public final class TeleportActionHandler {
} }
var config = mine.getConfig(); var config = mine.getConfig();
var newSpawn = PlayerSpawnConfig.at(player.getLocation()); var updatedConfig = withPlayerSpawn(config, PlayerSpawnConfig.at(player.getLocation()));
var updatedConfig = MineConfig.builder(mineName)
.region(config.region())
.blocks(config.blocks())
.reset(config.reset())
.rewards(config.rewards())
.teleport(config.teleport())
.ui(config.ui())
.worldGuard(config.worldGuard())
.playerSpawn(newSpawn)
.build();
plugin.getMines().getFileManager().saveConfig(updatedConfig); plugin.getMines().getFileManager().saveConfig(updatedConfig);
sender.sendMessage(MessageFormatter.success("Safe spawn location for mine '" + mineName + "' set to your current position")); sender.sendMessage(MessageFormatter.success("Safe spawn location for mine '" + mineName + "' set to your current position"));
@ -108,16 +86,7 @@ public final class TeleportActionHandler {
} }
var config = mine.getConfig(); var config = mine.getConfig();
var updatedConfig = MineConfig.builder(mineName) var updatedConfig = withPlayerSpawn(config, PlayerSpawnConfig.disabled());
.region(config.region())
.blocks(config.blocks())
.reset(config.reset())
.rewards(config.rewards())
.teleport(config.teleport())
.ui(config.ui())
.worldGuard(config.worldGuard())
.playerSpawn(PlayerSpawnConfig.disabled())
.build();
plugin.getMines().getFileManager().saveConfig(updatedConfig); plugin.getMines().getFileManager().saveConfig(updatedConfig);
sender.sendMessage(MessageFormatter.success("Spawn location for mine '" + mineName + "' cleared")); sender.sendMessage(MessageFormatter.success("Spawn location for mine '" + mineName + "' cleared"));
@ -128,6 +97,32 @@ public final class TeleportActionHandler {
} }
} }
private MineConfig withTeleport(MineConfig config, TeleportConfig teleport) {
return MineConfig.builder(config.name())
.region(config.region())
.blocks(config.blocks())
.reset(config.reset())
.rewards(config.rewards())
.teleport(teleport)
.ui(config.ui())
.worldGuard(config.worldGuard())
.playerSpawn(config.playerSpawn())
.build();
}
private MineConfig withPlayerSpawn(MineConfig config, PlayerSpawnConfig spawn) {
return MineConfig.builder(config.name())
.region(config.region())
.blocks(config.blocks())
.reset(config.reset())
.rewards(config.rewards())
.teleport(config.teleport())
.ui(config.ui())
.worldGuard(config.worldGuard())
.playerSpawn(spawn)
.build();
}
public String formatLocation(Location loc) { public String formatLocation(Location loc) {
return String.format("%.1f, %.1f, %.1f in %s", return String.format("%.1f, %.1f, %.1f in %s",
loc.getX(), loc.getY(), loc.getZ(), loc.getWorld().getName()); loc.getX(), loc.getY(), loc.getZ(), loc.getWorld().getName());

View file

@ -3,7 +3,6 @@ package dev.loki.lomines.command.admin.stats;
import dev.loki.lomines.LoMinesPlugin; import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.core.mine.model.Mine; import dev.loki.lomines.core.mine.model.Mine;
import dev.loki.lomines.core.mine.registry.Mines; import dev.loki.lomines.core.mine.registry.Mines;
import dev.loki.lomines.data.stats.model.Leaderboard;
import dev.loki.lomines.data.stats.model.LeaderboardEntry; import dev.loki.lomines.data.stats.model.LeaderboardEntry;
import dev.loki.lomines.util.ErrorHandler; import dev.loki.lomines.util.ErrorHandler;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
@ -77,29 +76,37 @@ public final class LeaderboardRenderer {
} }
public static void renderTopCommand(CommandSender sender, String arg1, String arg2, Mines mines, LoMinesPlugin plugin) { public static void renderTopCommand(CommandSender sender, String arg1, String arg2, Mines mines, LoMinesPlugin plugin) {
TopRequest req = parseTopRequest(arg1, arg2);
if (req.mineName() != null) {
renderMineTop(sender, req.mineName(), req.limit(), mines, plugin);
return;
}
List<LeaderboardEntry> rows = plugin.getStatsManager().getLeaderboard().getTopTotal(req.limit());
sender.sendMessage(Component.text("=== Общий топ ===", NamedTextColor.GOLD));
sendTopRows(sender, rows);
}
private record TopRequest(int limit, String mineName) {
}
private static TopRequest parseTopRequest(String arg1, String arg2) {
String a = arg1 == null ? "" : arg1.trim(); String a = arg1 == null ? "" : arg1.trim();
String b = arg2 == null ? "" : arg2.trim(); String b = arg2 == null ? "" : arg2.trim();
int limit;
String mineName;
if (a.isEmpty()) { if (a.isEmpty()) {
limit = clampLimit(10); return new TopRequest(clampLimit(10), null);
mineName = null; }
} else if (b.isEmpty()) { if (b.isEmpty()) {
Integer asLimit = tryParsePositiveInt(a); Integer asLimit = tryParsePositiveInt(a);
if (asLimit != null) { if (asLimit != null) {
limit = clampLimit(asLimit); return new TopRequest(clampLimit(asLimit), null);
mineName = null; }
} else { return new TopRequest(clampLimit(10), a);
limit = clampLimit(10);
mineName = a;
} }
} else {
mineName = a;
Integer limParsed = tryParsePositiveInt(b); Integer limParsed = tryParsePositiveInt(b);
limit = clampLimit(limParsed != null ? limParsed : 10); return new TopRequest(clampLimit(limParsed != null ? limParsed : 10), a);
} }
Leaderboard lb = plugin.getStatsManager().getLeaderboard();
if (mineName != null) { private static void renderMineTop(CommandSender sender, String mineName, int limit, Mines mines, LoMinesPlugin plugin) {
var found = mines.find(mineName); var found = mines.find(mineName);
if (found.isEmpty()) { if (found.isEmpty()) {
sender.sendMessage(Component.text("Шахта не найдена: " + mineName, NamedTextColor.RED)); sender.sendMessage(Component.text("Шахта не найдена: " + mineName, NamedTextColor.RED));
@ -107,13 +114,8 @@ public final class LeaderboardRenderer {
} }
Mine m = found.get(); Mine m = found.get();
String key = m.getName(); String key = m.getName();
List<LeaderboardEntry> rows = lb.getTopByMine(key, limit); List<LeaderboardEntry> rows = plugin.getStatsManager().getLeaderboard().getTopByMine(key, limit);
sender.sendMessage(Component.text("=== Топ по шахте \u00ab" + key + "\u00bb ===", NamedTextColor.GOLD)); sender.sendMessage(Component.text("=== Топ по шахте \u00ab" + key + "\u00bb ===", NamedTextColor.GOLD));
sendTopRows(sender, rows); sendTopRows(sender, rows);
return;
}
List<LeaderboardEntry> rows = lb.getTopTotal(limit);
sender.sendMessage(Component.text("=== Общий топ ===", NamedTextColor.GOLD));
sendTopRows(sender, rows);
} }
} }

View file

@ -26,7 +26,20 @@ public class StatsCommands {
public void handle(CommandSender sender, String subcommand, String[] args) { public void handle(CommandSender sender, String subcommand, String[] args) {
switch (subcommand.toLowerCase()) { switch (subcommand.toLowerCase()) {
case "stats" -> { case "stats" -> handleStats(sender, args);
case "top" -> {
if (isStatsDisabled(sender)) {
return;
}
String arg1 = args.length > 0 ? args[0] : "";
String arg2 = args.length > 1 ? args[1] : "";
LeaderboardRenderer.renderTopCommand(sender, arg1, arg2, mines, plugin);
}
default -> {}
}
}
private void handleStats(CommandSender sender, String[] args) {
if (isStatsDisabled(sender)) { if (isStatsDisabled(sender)) {
return; return;
} }
@ -56,17 +69,6 @@ public class StatsCommands {
.forEach(e -> sender.sendMessage(Component.text( .forEach(e -> sender.sendMessage(Component.text(
" " + e.getKey() + ": " + e.getValue(), NamedTextColor.DARK_AQUA))); " " + e.getKey() + ": " + e.getValue(), NamedTextColor.DARK_AQUA)));
} }
case "top" -> {
if (isStatsDisabled(sender)) {
return;
}
String arg1 = args.length > 0 ? args[0] : "";
String arg2 = args.length > 1 ? args[1] : "";
LeaderboardRenderer.renderTopCommand(sender, arg1, arg2, mines, plugin);
}
default -> {}
}
}
private boolean isStatsDisabled(CommandSender sender) { private boolean isStatsDisabled(CommandSender sender) {
if (!plugin.getConfig().getBoolean("statistics-enabled", true)) { if (!plugin.getConfig().getBoolean("statistics-enabled", true)) {

View file

@ -35,19 +35,8 @@ public class TeleportCommand {
return; return;
} }
var config = mine.getConfig(); Location target = resolveTarget(player, mine);
Location target; if (target == null) {
if (config.teleport().enabled() && config.teleport().getLocation().isPresent()) {
target = config.teleport().getLocation().get();
} else if (!mine.getRegions().isEmpty()) {
Cuboid region = mine.getRegions().get(0);
double centerX = (region.getMinX() + region.getMaxX()) / 2.0 + 0.5;
double centerZ = (region.getMinZ() + region.getMaxZ()) / 2.0 + 0.5;
double centerY = region.getMaxY() + 1;
target = new Location(region.getWorld(), centerX, centerY, centerZ);
} else {
player.sendMessage(Component.text("У шахты нет настроенной точки телепорта!", NamedTextColor.RED));
return; return;
} }
@ -55,4 +44,20 @@ public class TeleportCommand {
player.sendMessage(Component.text("§aТелепортировано к шахте §f" + mine.getName() + player.sendMessage(Component.text("§aТелепортировано к шахте §f" + mine.getName() +
" §a(§f" + String.format("%.0f, %.0f, %.0f", target.getX(), target.getY(), target.getZ()) + "§a)")); " §a(§f" + String.format("%.0f, %.0f, %.0f", target.getX(), target.getY(), target.getZ()) + "§a)"));
} }
private Location resolveTarget(Player player, Mine mine) {
var config = mine.getConfig();
if (config.teleport().enabled() && config.teleport().getLocation().isPresent()) {
return config.teleport().getLocation().get();
}
if (!mine.getRegions().isEmpty()) {
Cuboid region = mine.getRegions().get(0);
double centerX = (region.getMinX() + region.getMaxX()) / 2.0 + 0.5;
double centerZ = (region.getMinZ() + region.getMaxZ()) / 2.0 + 0.5;
double centerY = region.getMaxY() + 1;
return new Location(region.getWorld(), centerX, centerY, centerZ);
}
player.sendMessage(Component.text("У шахты нет настроенной точки телепорта!", NamedTextColor.RED));
return null;
}
} }

View file

@ -1,11 +0,0 @@
/**
* Core components of the LoMines plugin.
* <p>
* This package contains the main plugin class and core system classes:
* <ul>
* <li>{@code LoMinesPlugin} - Main plugin class extending LoPlugin</li>
* <li>{@code Mines} - Registry for managing all active mines</li>
* <li>{@code Mine} - Representation of a single mine with its configuration and state</li>
* </ul>
*/
package dev.loki.lomines.core.common;

View file

@ -44,6 +44,7 @@ public final class Mines {
} }
public void create(String name, Location corner1, Location corner2) throws IOException { public void create(String name, Location corner1, Location corner2) throws IOException {
name = name.toLowerCase().trim();
if (repository.exists(name)) { if (repository.exists(name)) {
throw new IllegalArgumentException("Mine already exists: " + name); throw new IllegalArgumentException("Mine already exists: " + name);
} }

View file

@ -42,9 +42,13 @@ public final class BlockConfigLoader {
yaml.getString("blocks.fill-mode", "CUBOID").toUpperCase() yaml.getString("blocks.fill-mode", "CUBOID").toUpperCase()
); );
// Parse mask config if applicable return new BlockConfig(weights, fillMode, parseMaskConfig(yaml, fillMode));
BlockConfig.MaskConfig mask = null; }
if (fillMode == FillMode.MASK) {
private BlockConfig.MaskConfig parseMaskConfig(YamlConfiguration yaml, FillMode fillMode) {
if (fillMode != FillMode.MASK) {
return null;
}
String markerStr = yaml.getString("blocks.mask.marker", "pink_concrete"); String markerStr = yaml.getString("blocks.mask.marker", "pink_concrete");
BlockKey marker = BlockKey.deserialize(markerStr); BlockKey marker = BlockKey.deserialize(markerStr);
@ -54,10 +58,7 @@ public final class BlockConfigLoader {
positions.put(pos, true); positions.put(pos, true);
} }
mask = new BlockConfig.MaskConfig(marker, positions); return new BlockConfig.MaskConfig(marker, positions);
}
return new BlockConfig(weights, fillMode, mask);
} }
/** /**

View file

@ -22,44 +22,14 @@ public final class WorldGuardConfigLoader {
* Supports any WorldGuard flags. Default: block-break=allow. * Supports any WorldGuard flags. Default: block-break=allow.
*/ */
public WorldGuardConfig parse(YamlConfiguration yaml) { public WorldGuardConfig parse(YamlConfiguration yaml) {
ConfigurationSection wgSection = yaml.getConfigurationSection("worldguard"); if (!isEnabled(yaml)) {
if (wgSection == null) {
// Check defaults
boolean defaultEnabled = defaults.getBoolean("worldguard.enabled", false);
if (!defaultEnabled) {
return WorldGuardConfig.disabled();
}
}
boolean enabled = yaml.getBoolean("worldguard.enabled",
defaults.getBoolean("worldguard.enabled", false));
if (!enabled) {
return WorldGuardConfig.disabled(); return WorldGuardConfig.disabled();
} }
List<String> owners = yaml.getStringList("worldguard.owners"); List<String> owners = resolveOwners(yaml);
if (owners.isEmpty()) { List<String> members = resolveMembers(yaml);
owners = defaults.getStringList("worldguard.owners"); List<String> flags = resolveFlags(yaml);
} boolean protect = resolveProtect(yaml);
List<String> members = yaml.getStringList("worldguard.members");
if (members.isEmpty()) {
members = defaults.getStringList("worldguard.members");
}
// Support any WorldGuard flags. Default: only block-break=allow
List<String> flags = yaml.getStringList("worldguard.flags");
if (flags.isEmpty()) {
flags = defaults.getStringList("worldguard.flags");
}
if (flags.isEmpty()) {
flags = List.of("block-break=allow");
}
boolean protect = yaml.getBoolean("worldguard.protect-on-create",
defaults.getBoolean("worldguard.protect-on-create", true));
String tmpl = yaml.getString("worldguard.region-template", String tmpl = yaml.getString("worldguard.region-template",
defaults.getString("worldguard.region-template", "{mine_name}_{random_4}")); defaults.getString("worldguard.region-template", "{mine_name}_{random_4}"));
@ -73,6 +43,50 @@ public final class WorldGuardConfigLoader {
.build(); .build();
} }
private boolean isEnabled(YamlConfiguration yaml) {
ConfigurationSection wgSection = yaml.getConfigurationSection("worldguard");
if (wgSection == null) {
boolean defaultEnabled = defaults.getBoolean("worldguard.enabled", false);
if (!defaultEnabled) {
return false;
}
}
return yaml.getBoolean("worldguard.enabled", defaults.getBoolean("worldguard.enabled", false));
}
private List<String> resolveOwners(YamlConfiguration yaml) {
List<String> owners = yaml.getStringList("worldguard.owners");
if (owners.isEmpty()) {
owners = defaults.getStringList("worldguard.owners");
}
return owners;
}
private List<String> resolveMembers(YamlConfiguration yaml) {
List<String> members = yaml.getStringList("worldguard.members");
if (members.isEmpty()) {
members = defaults.getStringList("worldguard.members");
}
return members;
}
private List<String> resolveFlags(YamlConfiguration yaml) {
// Support any WorldGuard flags. Default: only block-break=allow
List<String> flags = yaml.getStringList("worldguard.flags");
if (flags.isEmpty()) {
flags = defaults.getStringList("worldguard.flags");
}
if (flags.isEmpty()) {
flags = List.of("block-break=allow");
}
return flags;
}
private boolean resolveProtect(YamlConfiguration yaml) {
return yaml.getBoolean("worldguard.protect-on-create",
defaults.getBoolean("worldguard.protect-on-create", true));
}
/** /**
* Saves WorldGuard configuration to YAML. * Saves WorldGuard configuration to YAML.
*/ */

View file

@ -36,16 +36,31 @@ public final class RewardConfigLoader {
} }
private RewardEntry parseRewardEntry(Map<?, ?> map) { private RewardEntry parseRewardEntry(Map<?, ?> map) {
List<BlockKey> blocks = parseBlocks(map.get("blocks"));
List<ItemReward> items = parseItems(map.get("items"));
List<String> commands = parseCommands(map.get("commands"));
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);
}
private List<BlockKey> parseBlocks(Object rawBlocks) {
List<BlockKey> blocks = new ArrayList<>(); List<BlockKey> blocks = new ArrayList<>();
List<?> blockList = (List<?>) map.get("blocks"); List<?> blockList = (List<?>) rawBlocks;
if (blockList != null) { if (blockList != null) {
for (Object b : blockList) { for (Object b : blockList) {
blocks.add(BlockKey.deserialize(b.toString())); blocks.add(BlockKey.deserialize(b.toString()));
} }
} }
return blocks;
}
private List<ItemReward> parseItems(Object rawItems) {
List<ItemReward> items = new ArrayList<>(); List<ItemReward> items = new ArrayList<>();
List<?> itemList = (List<?>) map.get("items"); List<?> itemList = (List<?>) rawItems;
if (itemList != null) { if (itemList != null) {
for (Object i : itemList) { for (Object i : itemList) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -53,9 +68,11 @@ public final class RewardConfigLoader {
items.add(parseItemReward(itemMap)); items.add(parseItemReward(itemMap));
} }
} }
return items;
}
private List<String> parseCommands(Object commandsObj) {
List<String> commands = new ArrayList<>(); List<String> commands = new ArrayList<>();
Object commandsObj = map.get("commands");
if (commandsObj instanceof List<?> list) { if (commandsObj instanceof List<?> list) {
for (Object obj : list) { for (Object obj : list) {
if (obj != null) { if (obj != null) {
@ -63,12 +80,7 @@ public final class RewardConfigLoader {
} }
} }
} }
Object preventDropsObj = map.get("prevent-drops"); return commands;
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);
} }
private ItemReward parseItemReward(Map<String, Object> map) { private ItemReward parseItemReward(Map<String, Object> map) {

View file

@ -29,8 +29,13 @@ public final class RewardParser {
} }
for (int i = 0; i < rewardsList.size(); i++) { for (int i = 0; i < rewardsList.size(); i++) {
Object rewardObj = rewardsList.get(i); rewards.add(parseRewardAt(rewardsList.get(i), i));
}
return rewards;
}
private Reward parseRewardAt(Object rewardObj, int i) throws ConfigParseException {
if (!(rewardObj instanceof Map)) { if (!(rewardObj instanceof Map)) {
throw new ConfigParseException( throw new ConfigParseException(
"Invalid reward at index " + i + ": expected map, got " + "Invalid reward at index " + i + ": expected map, got " +
@ -42,8 +47,7 @@ public final class RewardParser {
Map<String, Object> rewardMap = (Map<String, Object>) rewardObj; Map<String, Object> rewardMap = (Map<String, Object>) rewardObj;
try { try {
Reward reward = entryParser.parseReward(rewardMap); return entryParser.parseReward(rewardMap);
rewards.add(reward);
} catch (ConfigParseException e) { } catch (ConfigParseException e) {
throw new ConfigParseException( throw new ConfigParseException(
"Error parsing reward at index " + i + ": " + e.getMessage(), "Error parsing reward at index " + i + ": " + e.getMessage(),
@ -51,7 +55,4 @@ public final class RewardParser {
); );
} }
} }
return rewards;
}
} }

View file

@ -43,12 +43,19 @@ public final class StatsPersistence {
} }
for (String uuidString : playersSection.getKeys(false)) { for (String uuidString : playersSection.getKeys(false)) {
loadPlayer(stats, uuidString, playersSection);
}
plugin.getLogger().info("Loaded statistics for " + stats.size() + " player(s)");
}
private void loadPlayer(Map<UUID, PlayerStats> stats, String uuidString, ConfigurationSection playersSection) {
try { try {
UUID playerId = UUID.fromString(uuidString); UUID playerId = UUID.fromString(uuidString);
ConfigurationSection playerSection = playersSection.getConfigurationSection(uuidString); ConfigurationSection playerSection = playersSection.getConfigurationSection(uuidString);
if (playerSection == null) { if (playerSection == null) {
continue; return;
} }
PlayerStats playerStats = stats.computeIfAbsent(playerId, PlayerStats::new); PlayerStats playerStats = stats.computeIfAbsent(playerId, PlayerStats::new);
@ -68,9 +75,6 @@ public final class StatsPersistence {
} }
} }
plugin.getLogger().info("Loaded statistics for " + stats.size() + " player(s)");
}
public void save(Map<UUID, PlayerStats> stats) throws IOException { public void save(Map<UUID, PlayerStats> stats) throws IOException {
YamlConfiguration yaml = new YamlConfiguration(); YamlConfiguration yaml = new YamlConfiguration();

View file

@ -94,6 +94,20 @@ public final class GroupCreateGui {
return; return;
} }
CreationResult result = createSlots(plugin, player, session, base);
if (result.created() == 0) {
player.sendMessage(Component.text("Нет готовых слотов (2 точки в каждом).", NamedTextColor.RED));
return;
}
player.sendMessage(Component.text(
"Создано шахт: " + result.created() + (result.skipped() > 0 ? " (пропущено: " + result.skipped() + ")" : ""),
NamedTextColor.GREEN));
}
private record CreationResult(int created, int skipped) {
}
private static CreationResult createSlots(LoMinesPlugin plugin, Player player, GroupWandSession session, String base) {
Mines mines = plugin.getMines(); Mines mines = plugin.getMines();
int created = 0; int created = 0;
int skipped = 0; int skipped = 0;
@ -115,13 +129,6 @@ public final class GroupCreateGui {
e.printStackTrace(); e.printStackTrace();
} }
} }
return new CreationResult(created, skipped);
if (created == 0) {
player.sendMessage(Component.text("Нет готовых слотов (2 точки в каждом).", NamedTextColor.RED));
return;
}
player.sendMessage(Component.text(
"Создано шахт: " + created + (skipped > 0 ? " (пропущено: " + skipped + ")" : ""),
NamedTextColor.GREEN));
} }
} }

View file

@ -30,19 +30,32 @@ public final class BlockWeightEditor {
Map<BlockKey, Double> weights = new java.util.HashMap<>(config.weights()); Map<BlockKey, Double> weights = new java.util.HashMap<>(config.weights());
double currentWeight = weights.getOrDefault(blockKey, 0.0) * 100.0; double currentWeight = weights.getOrDefault(blockKey, 0.0) * 100.0;
double delta = 0; double newWeight = Math.max(0, Math.min(100,
if (leftClick && shiftClick) { currentWeight + computeDelta(leftClick, shiftClick, rightClick)));
delta = 1.0;
} else if (rightClick && shiftClick) { applyWeightChange(player, blockKey, weights, newWeight, rightClick, shiftClick);
delta = -1.0;
} else if (leftClick) { BlocksGui.refresh(player, player.getOpenInventory().getTopInventory(), mineName, plugin);
delta = 5.0;
} else if (rightClick) {
delta = -5.0;
} }
double newWeight = Math.max(0, Math.min(100, currentWeight + delta)); private static double computeDelta(boolean leftClick, boolean shiftClick, boolean rightClick) {
if (leftClick && shiftClick) {
return 1.0;
}
if (rightClick && shiftClick) {
return -1.0;
}
if (leftClick) {
return 5.0;
}
if (rightClick) {
return -5.0;
}
return 0;
}
private static void applyWeightChange(Player player, BlockKey blockKey, Map<BlockKey, Double> weights,
double newWeight, boolean rightClick, boolean shiftClick) {
if (rightClick && shiftClick && newWeight <= 0.1) { if (rightClick && shiftClick && newWeight <= 0.1) {
weights.remove(blockKey); weights.remove(blockKey);
player.sendMessage(Component.text( player.sendMessage(Component.text(
@ -53,8 +66,6 @@ public final class BlockWeightEditor {
"\u00a7a\u0412\u0435\u0441 \u00a7f" + blockKey.serialize() + " \u00a7a\u0438\u0437\u043c\u0435\u043d\u0435\u043d: \u00a7f" "\u00a7a\u0412\u0435\u0441 \u00a7f" + blockKey.serialize() + " \u00a7a\u0438\u0437\u043c\u0435\u043d\u0435\u043d: \u00a7f"
+ String.format("%.1f%%", newWeight))); + String.format("%.1f%%", newWeight)));
} }
BlocksGui.refresh(player, player.getOpenInventory().getTopInventory(), mineName, plugin);
} }
public static BlockKey getBlockAtSlot(Inventory inv, int slot, LoMinesPlugin plugin, String mineName) { public static BlockKey getBlockAtSlot(Inventory inv, int slot, LoMinesPlugin plugin, String mineName) {

View file

@ -87,14 +87,7 @@ public final class ResetGui {
} }
if (rawSlot == SLOT_MESSAGE) { if (rawSlot == SLOT_MESSAGE) {
if (leftClick) { return handleMessageSlot(plugin, player, mineName, leftClick, rightClick);
player.sendMessage(Component.text("§eВведите сообщение в чат (или 'cancel' для отмены):"));
player.closeInventory();
} else if (rightClick) {
player.sendMessage(Component.text("§cСообщение очищено."));
refresh(player, player.getOpenInventory().getTopInventory(), mineName, plugin);
}
return true;
} }
if (rawSlot == SLOT_COMMANDS && leftClick) { if (rawSlot == SLOT_COMMANDS && leftClick) {
@ -105,6 +98,18 @@ public final class ResetGui {
return true; return true;
} }
private static boolean handleMessageSlot(LoMinesPlugin plugin, Player player, String mineName,
boolean leftClick, boolean rightClick) {
if (leftClick) {
player.sendMessage(Component.text("§eВведите сообщение в чат (или 'cancel' для отмены):"));
player.closeInventory();
} else if (rightClick) {
player.sendMessage(Component.text("§cСообщение очищено."));
refresh(player, player.getOpenInventory().getTopInventory(), mineName, plugin);
}
return true;
}
private static void adjustInterval(LoMinesPlugin plugin, Player player, String mineName, private static void adjustInterval(LoMinesPlugin plugin, Player player, String mineName,
boolean leftClick, boolean rightClick, boolean shiftClick) { boolean leftClick, boolean rightClick, boolean shiftClick) {
int delta = 0; int delta = 0;

View file

@ -81,11 +81,11 @@ public final class MineEditGui {
if (rawSlot < 0 || rawSlot >= SIZE) { if (rawSlot < 0 || rawSlot >= SIZE) {
return false; return false;
} }
switch (rawSlot) { return handleNavigationSlot(plugin, player, mineName, rawSlot);
case SLOT_BACK -> {
player.closeInventory();
return true;
} }
private static boolean handleNavigationSlot(LoMinesPlugin plugin, Player player, String mineName, int rawSlot) {
switch (rawSlot) {
case SLOT_BLOCKS -> { case SLOT_BLOCKS -> {
BlocksGui.open(plugin, player, mineName); BlocksGui.open(plugin, player, mineName);
return true; return true;
@ -98,6 +98,22 @@ public final class MineEditGui {
RewardsGui.open(plugin, player, mineName); RewardsGui.open(plugin, player, mineName);
return true; return true;
} }
case SLOT_DELETE -> {
ConfirmDeleteGui.open(plugin, player, mineName);
return true;
}
default -> {
return handleInfoSlot(player, mineName, rawSlot);
}
}
}
private static boolean handleInfoSlot(Player player, String mineName, int rawSlot) {
switch (rawSlot) {
case SLOT_BACK -> {
player.closeInventory();
return true;
}
case SLOT_TELEPORT -> { case SLOT_TELEPORT -> {
player.sendMessage(Component.text("Используйте команды /lm setteleport и /lm setspawn", NamedTextColor.YELLOW)); player.sendMessage(Component.text("Используйте команды /lm setteleport и /lm setspawn", NamedTextColor.YELLOW));
return true; return true;
@ -111,10 +127,6 @@ public final class MineEditGui {
player.closeInventory(); player.closeInventory();
return true; return true;
} }
case SLOT_DELETE -> {
ConfirmDeleteGui.open(plugin, player, mineName);
return true;
}
case SLOT_INFO, SLOT_REGIONS -> { case SLOT_INFO, SLOT_REGIONS -> {
return true; return true;
} }

View file

@ -45,6 +45,14 @@ public final class WorldGuardRegionService {
} }
String regionName = wgConfig.generateRegionName(mineName); String regionName = wgConfig.generateRegionName(mineName);
try { try {
return createRegionInManager(mineName, config, regionName, wgConfig);
} catch (Exception e) {
plugin.getLogger().severe("Failed to create WorldGuard region for mine " + mineName + ": " + e.getMessage());
return null;
}
}
private String createRegionInManager(String mineName, MineConfig config, String regionName, WorldGuardConfig wgConfig) {
RegionManager regionManager = getRegionManager(config.worldName()); RegionManager regionManager = getRegionManager(config.worldName());
if (regionManager == null) { if (regionManager == null) {
return null; return null;
@ -63,10 +71,6 @@ public final class WorldGuardRegionService {
regionManager.addRegion(region); regionManager.addRegion(region);
plugin.getLogger().info("Created WorldGuard region '" + regionName + "' for mine " + mineName); plugin.getLogger().info("Created WorldGuard region '" + regionName + "' for mine " + mineName);
return regionName; return regionName;
} catch (Exception e) {
plugin.getLogger().severe("Failed to create WorldGuard region for mine " + mineName + ": " + e.getMessage());
return null;
}
} }
public boolean updateRegion(String mineName, MineConfig config) { public boolean updateRegion(String mineName, MineConfig config) {

View file

@ -48,7 +48,17 @@ public final class PlayerInteractListener implements Listener {
if (action == Action.LEFT_CLICK_BLOCK && event.hasBlock()) { if (action == Action.LEFT_CLICK_BLOCK && event.hasBlock()) {
event.setCancelled(true); event.setCancelled(true);
Block block = event.getClickedBlock(); handleLeftClick(player, event.getClickedBlock());
return;
}
if (action == Action.RIGHT_CLICK_BLOCK && !player.isSneaking()) {
event.setCancelled(true);
handleRightClick(player, event.getClickedBlock());
}
}
private void handleLeftClick(Player player, Block block) {
if (block == null) { if (block == null) {
return; return;
} }
@ -60,12 +70,9 @@ public final class PlayerInteractListener implements Listener {
// Start showing particles when selection is made // Start showing particles when selection is made
plugin.getWandParticleService().startShowingParticles(player); plugin.getWandParticleService().startShowingParticles(player);
return;
} }
if (action == Action.RIGHT_CLICK_BLOCK && !player.isSneaking()) { private void handleRightClick(Player player, Block block) {
event.setCancelled(true);
Block block = event.getClickedBlock();
if (block == null) { if (block == null) {
return; return;
} }
@ -78,7 +85,6 @@ public final class PlayerInteractListener implements Listener {
// Start showing particles when selection is made // Start showing particles when selection is made
plugin.getWandParticleService().startShowingParticles(player); plugin.getWandParticleService().startShowingParticles(player);
} }
}
/** /**
* Starts/stops particle visualization when player switches items. * Starts/stops particle visualization when player switches items.

View file

@ -42,6 +42,10 @@ public final class LocationParser {
); );
} }
return buildLocation(matcher, locationString);
}
private static Location buildLocation(Matcher matcher, String locationString) throws ConfigParseException {
try { try {
String worldName = matcher.group(1); String worldName = matcher.group(1);
double x = Double.parseDouble(matcher.group(2)); double x = Double.parseDouble(matcher.group(2));

View file

@ -40,6 +40,10 @@ public final class SafeTeleportUtil {
return destination; return destination;
} }
return findFallbackSafe(world, x, y, z, yaw, pitch, maxUpOffset);
}
private static Location findFallbackSafe(World world, int x, int y, int z, float yaw, float pitch, int maxUpOffset) {
Location result = findNearbySafe(world, x, y, z, yaw, pitch); Location result = findNearbySafe(world, x, y, z, yaw, pitch);
if (result != null) { if (result != null) {
return result; return result;

View file

@ -12,9 +12,6 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import static org.mockito.Mockito.any; import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.doThrow;

View file

@ -10,41 +10,47 @@ import dev.loki.lomines.util.location.geo.Cuboid;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.World; import org.bukkit.World;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockbukkit.mockbukkit.MockBukkit;
import org.mockbukkit.mockbukkit.ServerMock;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.when;
/** /**
* Unit tests for Mine class, specifically testing BlockSetter creation logic. * Unit tests for Mine class, specifically testing BlockSetter creation logic.
*/ */
@Disabled("Paper API RegistryAccess not available in unit tests")
class MineTest { class MineTest {
@Mock @Mock
private LoMinesPlugin plugin; private LoMinesPlugin plugin;
@Mock private ServerMock server;
private World world; private World world;
@BeforeEach @BeforeEach
void setUp() { void setUp() {
MockitoAnnotations.openMocks(this); MockitoAnnotations.openMocks(this);
when(world.getName()).thenReturn("world"); server = MockBukkit.mock();
server.addSimpleWorld("world");
world = server.getWorld("world");
}
@AfterEach
void tearDown() {
MockBukkit.unmock();
} }
@Test @Test
void testCreateBlockSetter_WithOraxenPrefix_CreatesBukkitBlockSetter() { void testCreateBlockSetterWithOraxenPrefixCreatesBukkitBlockSetter() {
// Oraxen integration is disabled — constructor throws IllegalArgumentException // Oraxen integration is disabled — constructor throws IllegalArgumentException
Map<BlockKey, Double> blocks = new HashMap<>(); Map<BlockKey, Double> blocks = new HashMap<>();
blocks.put(new BlockKey.Oraxen("custom_ore"), 50.0); blocks.put(new BlockKey.Oraxen("custom_ore"), 50.0);
@ -59,7 +65,7 @@ class MineTest {
} }
@Test @Test
void testCreateBlockSetter_WithItemsAdderPrefix_CreatesBukkitBlockSetter() { void testCreateBlockSetterWithItemsAdderPrefixCreatesBukkitBlockSetter() {
// ItemsAdder integration is disabled — constructor throws IllegalArgumentException // ItemsAdder integration is disabled — constructor throws IllegalArgumentException
Map<BlockKey, Double> blocks = new HashMap<>(); Map<BlockKey, Double> blocks = new HashMap<>();
blocks.put(new BlockKey.ItemsAdder("custom_block"), 60.0); blocks.put(new BlockKey.ItemsAdder("custom_block"), 60.0);
@ -73,7 +79,7 @@ class MineTest {
} }
@Test @Test
void testCreateBlockSetter_WithoutPrefix_CreatesBukkitBlockSetter() { void testCreateBlockSetterWithoutPrefixCreatesBukkitBlockSetter() {
Map<BlockKey, Double> blocks = new HashMap<>(); Map<BlockKey, Double> blocks = new HashMap<>();
blocks.put(new BlockKey.Vanilla(Material.STONE), 50.0); blocks.put(new BlockKey.Vanilla(Material.STONE), 50.0);
blocks.put(new BlockKey.Vanilla(Material.COAL_ORE), 30.0); blocks.put(new BlockKey.Vanilla(Material.COAL_ORE), 30.0);
@ -89,7 +95,7 @@ class MineTest {
} }
@Test @Test
void testCreateBlockSetter_WithEmptyBlocks_ThrowsException() { void testCreateBlockSetterWithEmptyBlocksThrowsException() {
Map<BlockKey, Double> blocks = new HashMap<>(); Map<BlockKey, Double> blocks = new HashMap<>();
assertThrows( assertThrows(

View file

@ -29,13 +29,13 @@ class MineTickerTest {
} }
@Test @Test
void testConstruction_WithValidParameters_DoesNotThrow() { void testConstructionWithValidParametersDoesNotThrow() {
// Act & Assert // Act & Assert
assertDoesNotThrow(() -> new MineTicker(mines, plugin)); assertDoesNotThrow(() -> new MineTicker(mines, plugin));
} }
@Test @Test
void testStop_WhenNotRunning_DoesNotThrow() { void testStopWhenNotRunningDoesNotThrow() {
// Arrange // Arrange
MineTicker ticker = new MineTicker(mines, plugin); MineTicker ticker = new MineTicker(mines, plugin);
@ -44,7 +44,7 @@ class MineTickerTest {
} }
@Test @Test
void testMultipleStops_DoNotThrow() { void testMultipleStopsDoNotThrow() {
// Arrange // Arrange
MineTicker ticker = new MineTicker(mines, plugin); MineTicker ticker = new MineTicker(mines, plugin);

View file

@ -3,19 +3,12 @@ package dev.loki.lomines.core;
import dev.loki.lomines.LoMinesPlugin; import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.core.mine.model.Mine; import dev.loki.lomines.core.mine.model.Mine;
import dev.loki.lomines.core.mine.registry.Mines; import dev.loki.lomines.core.mine.registry.Mines;
import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.block.data.BlockData;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;
import org.mockito.MockedStatic; import org.mockbukkit.mockbukkit.MockBukkit;
import org.mockbukkit.mockbukkit.ServerMock;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
@ -23,19 +16,13 @@ import java.nio.file.Path;
import java.util.logging.Logger; import java.util.logging.Logger;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@Disabled("WorldGuard dependency not available in unit tests")
class MinesLifecycleTest { class MinesLifecycleTest {
@TempDir @TempDir
@ -43,43 +30,25 @@ class MinesLifecycleTest {
private Mines mines; private Mines mines;
private LoMinesPlugin plugin; private LoMinesPlugin plugin;
private BukkitScheduler scheduler; private ServerMock server;
private MockedStatic<Bukkit> globalBukkit;
@BeforeEach @BeforeEach
void setUp() { void setUp() {
globalBukkit = mockStatic(Bukkit.class); server = MockBukkit.mock();
server.addSimpleWorld("world");
org.bukkit.plugin.PluginManager pluginManager = mock(org.bukkit.plugin.PluginManager.class);
Server server = mock(Server.class);
World world = mock(World.class);
BlockData blockData = mock(BlockData.class);
globalBukkit.when(Bukkit::getPluginManager).thenReturn(pluginManager);
globalBukkit.when(Bukkit::getServer).thenReturn(server);
globalBukkit.when(() -> Bukkit.getWorld(anyString())).thenReturn(world);
globalBukkit.when(() -> Bukkit.createBlockData(any(Material.class))).thenReturn(blockData);
when(world.getName()).thenReturn("world");
plugin = mock(LoMinesPlugin.class); plugin = mock(LoMinesPlugin.class);
Logger logger = mock(Logger.class); Logger logger = mock(Logger.class);
scheduler = mock(BukkitScheduler.class);
when(plugin.getDataFolder()).thenReturn(tempDir.toFile()); when(plugin.getDataFolder()).thenReturn(tempDir.toFile());
when(plugin.getLogger()).thenReturn(logger); when(plugin.getLogger()).thenReturn(logger);
globalBukkit.when(Bukkit::getScheduler).thenReturn(scheduler);
BukkitTask task = mock(BukkitTask.class);
when(scheduler.runTaskTimer(any(), any(Runnable.class), anyLong(), anyLong())).thenReturn(task);
mines = new Mines(plugin); mines = new Mines(plugin);
} }
@AfterEach @AfterEach
void tearDown() { void tearDown() {
if (globalBukkit != null) { MockBukkit.unmock();
globalBukkit.close();
}
} }
@Test @Test
@ -97,9 +66,9 @@ class MinesLifecycleTest {
@Test @Test
void testCreateMineCreatesFolder() throws IOException { void testCreateMineCreatesFolder() throws IOException {
assertFalse(Files.exists(tempDir.resolve("mines")));
mines.create("testmine");
assertTrue(Files.isDirectory(tempDir.resolve("mines"))); assertTrue(Files.isDirectory(tempDir.resolve("mines")));
mines.create("testmine");
assertTrue(Files.exists(tempDir.resolve("mines/testmine.yml")));
} }
@Test @Test

View file

@ -3,19 +3,12 @@ package dev.loki.lomines.core;
import dev.loki.lomines.LoMinesPlugin; import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.core.mine.model.Mine; import dev.loki.lomines.core.mine.model.Mine;
import dev.loki.lomines.core.mine.registry.Mines; import dev.loki.lomines.core.mine.registry.Mines;
import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.block.data.BlockData;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;
import org.mockito.MockedStatic; import org.mockbukkit.mockbukkit.MockBukkit;
import org.mockbukkit.mockbukkit.ServerMock;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
@ -25,15 +18,9 @@ import java.util.logging.Logger;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@Disabled("WorldGuard dependency not available in unit tests")
class MinesLoadingTest { class MinesLoadingTest {
private static final String FULL_YAML = """ private static final String FULL_YAML = """
@ -61,43 +48,25 @@ class MinesLoadingTest {
private Mines mines; private Mines mines;
private LoMinesPlugin plugin; private LoMinesPlugin plugin;
private BukkitScheduler scheduler; private ServerMock server;
private MockedStatic<Bukkit> globalBukkit;
@BeforeEach @BeforeEach
void setUp() { void setUp() {
globalBukkit = mockStatic(Bukkit.class); server = MockBukkit.mock();
server.addSimpleWorld("world");
org.bukkit.plugin.PluginManager pluginManager = mock(org.bukkit.plugin.PluginManager.class);
Server server = mock(Server.class);
World world = mock(World.class);
BlockData blockData = mock(BlockData.class);
globalBukkit.when(Bukkit::getPluginManager).thenReturn(pluginManager);
globalBukkit.when(Bukkit::getServer).thenReturn(server);
globalBukkit.when(() -> Bukkit.getWorld(anyString())).thenReturn(world);
globalBukkit.when(() -> Bukkit.createBlockData(any(Material.class))).thenReturn(blockData);
when(world.getName()).thenReturn("world");
plugin = mock(LoMinesPlugin.class); plugin = mock(LoMinesPlugin.class);
Logger logger = mock(Logger.class); Logger logger = mock(Logger.class);
scheduler = mock(BukkitScheduler.class);
when(plugin.getDataFolder()).thenReturn(tempDir.toFile()); when(plugin.getDataFolder()).thenReturn(tempDir.toFile());
when(plugin.getLogger()).thenReturn(logger); when(plugin.getLogger()).thenReturn(logger);
globalBukkit.when(Bukkit::getScheduler).thenReturn(scheduler);
BukkitTask task = mock(BukkitTask.class);
when(scheduler.runTaskTimer(any(), any(Runnable.class), anyLong(), anyLong())).thenReturn(task);
mines = new Mines(plugin); mines = new Mines(plugin);
} }
@AfterEach @AfterEach
void tearDown() { void tearDown() {
if (globalBukkit != null) { MockBukkit.unmock();
globalBukkit.close();
}
} }
@Test @Test

View file

@ -1,22 +1,13 @@
package dev.loki.lomines.core; package dev.loki.lomines.core;
import dev.loki.lomines.LoMinesPlugin; import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.core.mine.model.Mine;
import dev.loki.lomines.core.mine.registry.Mines; import dev.loki.lomines.core.mine.registry.Mines;
import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.plugin.PluginManager;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.block.data.BlockData;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;
import org.mockito.MockedStatic; import org.mockbukkit.mockbukkit.MockBukkit;
import org.mockbukkit.mockbukkit.ServerMock;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
@ -25,19 +16,15 @@ import java.util.logging.Logger;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
/** /**
* Unit tests for Mines registry. * Unit tests for Mines registry.
*/ */
@Disabled("WorldGuard dependency not available in unit tests")
class MinesTest { class MinesTest {
@TempDir @TempDir
@ -45,43 +32,25 @@ class MinesTest {
private Mines mines; private Mines mines;
private LoMinesPlugin plugin; private LoMinesPlugin plugin;
private BukkitScheduler scheduler; private ServerMock server;
private MockedStatic<Bukkit> globalBukkit;
@BeforeEach @BeforeEach
void setUp() { void setUp() {
globalBukkit = mockStatic(Bukkit.class); server = MockBukkit.mock();
server.addSimpleWorld("world");
PluginManager pluginManager = mock(PluginManager.class);
Server server = mock(Server.class);
World world = mock(World.class);
BlockData blockData = mock(BlockData.class);
globalBukkit.when(Bukkit::getPluginManager).thenReturn(pluginManager);
globalBukkit.when(Bukkit::getServer).thenReturn(server);
globalBukkit.when(() -> Bukkit.getWorld(anyString())).thenReturn(world);
globalBukkit.when(() -> Bukkit.createBlockData(any(Material.class))).thenReturn(blockData);
when(world.getName()).thenReturn("world");
plugin = mock(LoMinesPlugin.class); plugin = mock(LoMinesPlugin.class);
Logger logger = mock(Logger.class); Logger logger = mock(Logger.class);
scheduler = mock(BukkitScheduler.class);
when(plugin.getDataFolder()).thenReturn(tempDir.toFile()); when(plugin.getDataFolder()).thenReturn(tempDir.toFile());
when(plugin.getLogger()).thenReturn(logger); when(plugin.getLogger()).thenReturn(logger);
globalBukkit.when(Bukkit::getScheduler).thenReturn(scheduler);
BukkitTask task = mock(BukkitTask.class);
when(scheduler.runTaskTimer(any(), any(Runnable.class), anyLong(), anyLong())).thenReturn(task);
mines = new Mines(plugin); mines = new Mines(plugin);
} }
@AfterEach @AfterEach
void tearDown() { void tearDown() {
if (globalBukkit != null) { MockBukkit.unmock();
globalBukkit.close();
}
} }
@Test @Test

View file

@ -30,7 +30,7 @@ class LeaderboardPositionTest {
} }
@Test @Test
void testGetPosition_ReturnsCorrectPosition() { void testGetPositionReturnsCorrectPosition() {
UUID player1 = UUID.randomUUID(); UUID player1 = UUID.randomUUID();
UUID player2 = UUID.randomUUID(); UUID player2 = UUID.randomUUID();
UUID player3 = UUID.randomUUID(); UUID player3 = UUID.randomUUID();
@ -45,13 +45,13 @@ class LeaderboardPositionTest {
} }
@Test @Test
void testGetPosition_ReturnsMinusOneForNonExistentPlayer() { void testGetPositionReturnsMinusOneForNonExistentPlayer() {
UUID playerId = UUID.randomUUID(); UUID playerId = UUID.randomUUID();
assertEquals(-1, leaderboard.getPosition(playerId), "Should return -1 for non-existent player"); assertEquals(-1, leaderboard.getPosition(playerId), "Should return -1 for non-existent player");
} }
@Test @Test
void testGetPosition_ReturnsMinusOneForZeroBlocks() { void testGetPositionReturnsMinusOneForZeroBlocks() {
UUID playerId = UUID.randomUUID(); UUID playerId = UUID.randomUUID();
statsManager.getOrCreate(playerId).setTotalBlocks(0); statsManager.getOrCreate(playerId).setTotalBlocks(0);

View file

@ -32,7 +32,7 @@ class LeaderboardQueryTest {
} }
@Test @Test
void testGetTopByMine_SortedDescending() { void testGetTopByMineSortedDescending() {
UUID player1 = UUID.randomUUID(); UUID player1 = UUID.randomUUID();
UUID player2 = UUID.randomUUID(); UUID player2 = UUID.randomUUID();
UUID player3 = UUID.randomUUID(); UUID player3 = UUID.randomUUID();
@ -49,7 +49,7 @@ class LeaderboardQueryTest {
} }
@Test @Test
void testGetTopByMine_FiltersZeroBlocks() { void testGetTopByMineFiltersZeroBlocks() {
UUID player1 = UUID.randomUUID(); UUID player1 = UUID.randomUUID();
UUID player2 = UUID.randomUUID(); UUID player2 = UUID.randomUUID();
@ -62,7 +62,7 @@ class LeaderboardQueryTest {
} }
@Test @Test
void testGetTopByMine_OnlyIncludesSpecificMine() { void testGetTopByMineOnlyIncludesSpecificMine() {
UUID player1 = UUID.randomUUID(); UUID player1 = UUID.randomUUID();
UUID player2 = UUID.randomUUID(); UUID player2 = UUID.randomUUID();

View file

@ -39,13 +39,13 @@ class LeaderboardTest {
} }
@Test @Test
void testGetTopTotal_EmptyStats() { void testGetTopTotalEmptyStats() {
List<LeaderboardEntry> top = leaderboard.getTopTotal(10); List<LeaderboardEntry> top = leaderboard.getTopTotal(10);
assertTrue(top.isEmpty(), "Top list should be empty when no stats exist"); assertTrue(top.isEmpty(), "Top list should be empty when no stats exist");
} }
@Test @Test
void testGetTopTotal_SinglePlayer() { void testGetTopTotalSinglePlayer() {
UUID playerId = UUID.randomUUID(); UUID playerId = UUID.randomUUID();
PlayerStats stats = statsManager.getOrCreate(playerId); PlayerStats stats = statsManager.getOrCreate(playerId);
stats.setTotalBlocks(100); stats.setTotalBlocks(100);
@ -57,7 +57,7 @@ class LeaderboardTest {
} }
@Test @Test
void testGetTopTotal_SortedDescending() { void testGetTopTotalSortedDescending() {
UUID player1 = UUID.randomUUID(); UUID player1 = UUID.randomUUID();
UUID player2 = UUID.randomUUID(); UUID player2 = UUID.randomUUID();
UUID player3 = UUID.randomUUID(); UUID player3 = UUID.randomUUID();
@ -74,7 +74,7 @@ class LeaderboardTest {
} }
@Test @Test
void testGetTopTotal_LimitRespected() { void testGetTopTotalLimitRespected() {
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
UUID playerId = UUID.randomUUID(); UUID playerId = UUID.randomUUID();
statsManager.getOrCreate(playerId).setTotalBlocks(i + 1); statsManager.getOrCreate(playerId).setTotalBlocks(i + 1);
@ -85,7 +85,7 @@ class LeaderboardTest {
} }
@Test @Test
void testGetTopTotal_FiltersZeroBlocks() { void testGetTopTotalFiltersZeroBlocks() {
UUID player1 = UUID.randomUUID(); UUID player1 = UUID.randomUUID();
UUID player2 = UUID.randomUUID(); UUID player2 = UUID.randomUUID();
@ -98,7 +98,7 @@ class LeaderboardTest {
} }
@Test @Test
void testGetTopTotal_CachingWorks() { void testGetTopTotalCachingWorks() {
UUID playerId = UUID.randomUUID(); UUID playerId = UUID.randomUUID();
statsManager.getOrCreate(playerId).setTotalBlocks(100); statsManager.getOrCreate(playerId).setTotalBlocks(100);
@ -113,7 +113,7 @@ class LeaderboardTest {
} }
@Test @Test
void testInvalidateCache_ClearsCache() { void testInvalidateCacheClearsCache() {
UUID playerId = UUID.randomUUID(); UUID playerId = UUID.randomUUID();
statsManager.getOrCreate(playerId).setTotalBlocks(100); statsManager.getOrCreate(playerId).setTotalBlocks(100);

View file

@ -1,7 +1,6 @@
package dev.loki.lomines.data; package dev.loki.lomines.data;
import dev.loki.lomines.LoMinesPlugin; import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.data.stats.model.PlayerStats;
import dev.loki.lomines.data.stats.service.StatsManager; import dev.loki.lomines.data.stats.service.StatsManager;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.scheduler.BukkitScheduler;
@ -11,7 +10,6 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;
import org.mockito.MockedStatic; import org.mockito.MockedStatic;
import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.UUID; import java.util.UUID;
import java.util.logging.Logger; import java.util.logging.Logger;

View file

@ -3,7 +3,6 @@ package dev.loki.lomines.data;
import dev.loki.lomines.LoMinesPlugin; import dev.loki.lomines.LoMinesPlugin;
import dev.loki.lomines.data.stats.model.PlayerStats; import dev.loki.lomines.data.stats.model.PlayerStats;
import dev.loki.lomines.data.stats.service.StatsManager; import dev.loki.lomines.data.stats.service.StatsManager;
import org.bukkit.configuration.file.YamlConfiguration;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;

View file

@ -8,7 +8,6 @@ import org.bukkit.configuration.file.YamlConfiguration;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.api.io.TempDir;
import org.mockito.MockedStatic;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;

View file

@ -10,14 +10,14 @@ import dev.loki.lomines.data.config.reset.ResetConfig;
import dev.loki.lomines.data.config.reward.RewardConfig; import dev.loki.lomines.data.config.reward.RewardConfig;
import dev.loki.lomines.data.config.teleport.TeleportConfig; import dev.loki.lomines.data.config.teleport.TeleportConfig;
import dev.loki.lomines.data.config.ui.UIConfig; import dev.loki.lomines.data.config.ui.UIConfig;
import dev.loki.lomines.util.location.geo.Cuboid;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.World; import org.bukkit.World;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic; import org.mockbukkit.mockbukkit.MockBukkit;
import org.mockito.Mockito; import org.mockbukkit.mockbukkit.ServerMock;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -25,26 +25,40 @@ import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@Disabled("Paper API RegistryAccess not available in unit tests")
class MineConfigTest { class MineConfigTest {
private ServerMock server;
private World world;
@BeforeEach
void setUp() {
server = MockBukkit.mock();
server.addSimpleWorld("world");
world = server.getWorld("world");
}
@AfterEach
void tearDown() {
MockBukkit.unmock();
}
private RegionConfig testRegion() {
Location loc1 = new Location(world, 0, 64, 0);
Location loc2 = new Location(world, 10, 74, 10);
return RegionConfig.fromSelections(List.of(loc1, loc2));
}
private BlockConfig testBlocks() {
Map<BlockKey, Double> weights = new HashMap<>();
weights.put(new BlockKey.Vanilla(Material.STONE), 100.0);
return new BlockConfig(weights, FillMode.CUBOID, null);
}
@Test @Test
void testBuilder() { void testBuilder() {
try (MockedStatic<org.bukkit.Bukkit> bukkit = Mockito.mockStatic(org.bukkit.Bukkit.class)) { RegionConfig region = testRegion();
World world = mock(World.class); BlockConfig blocks = testBlocks();
when(world.getName()).thenReturn("world");
bukkit.when(() -> org.bukkit.Bukkit.getWorld("world")).thenReturn(world);
Location loc1 = new Location(world, 0, 64, 0);
Location loc2 = new Location(world, 10, 74, 10);
RegionConfig region = RegionConfig.fromSelections(List.of(loc1, loc2));
Map<BlockKey, Double> weights = new HashMap<>();
weights.put(new BlockKey.Vanilla(Material.STONE), 100.0);
BlockConfig blocks = new BlockConfig(weights, FillMode.CUBOID, null);
MineConfig config = MineConfig.builder("testmine") MineConfig config = MineConfig.builder("testmine")
.region(region) .region(region)
@ -59,22 +73,11 @@ class MineConfigTest {
assertEquals(1, config.region().regionCount()); assertEquals(1, config.region().regionCount());
assertEquals(1, config.blocks().blockCount()); assertEquals(1, config.blocks().blockCount());
} }
}
@Test @Test
void testDefaultsFactory() { void testDefaultsFactory() {
try (MockedStatic<org.bukkit.Bukkit> bukkit = Mockito.mockStatic(org.bukkit.Bukkit.class)) { RegionConfig region = testRegion();
World world = mock(World.class); BlockConfig blocks = testBlocks();
when(world.getName()).thenReturn("world");
bukkit.when(() -> org.bukkit.Bukkit.getWorld("world")).thenReturn(world);
Location loc1 = new Location(world, 0, 64, 0);
Location loc2 = new Location(world, 10, 74, 10);
RegionConfig region = RegionConfig.fromSelections(List.of(loc1, loc2));
Map<BlockKey, Double> weights = new HashMap<>();
weights.put(new BlockKey.Vanilla(Material.STONE), 100.0);
BlockConfig blocks = new BlockConfig(weights, FillMode.CUBOID, null);
MineConfig config = MineConfigDefaults.defaults("mymine", region, blocks); MineConfig config = MineConfigDefaults.defaults("mymine", region, blocks);
@ -84,22 +87,11 @@ class MineConfigTest {
assertNotNull(config.teleport()); assertNotNull(config.teleport());
assertNotNull(config.ui()); assertNotNull(config.ui());
} }
}
@Test @Test
void testNameNormalized() { void testNameNormalized() {
try (MockedStatic<org.bukkit.Bukkit> bukkit = Mockito.mockStatic(org.bukkit.Bukkit.class)) { RegionConfig region = testRegion();
World world = mock(World.class); BlockConfig blocks = testBlocks();
when(world.getName()).thenReturn("world");
bukkit.when(() -> org.bukkit.Bukkit.getWorld("world")).thenReturn(world);
Location loc1 = new Location(world, 0, 64, 0);
Location loc2 = new Location(world, 10, 74, 10);
RegionConfig region = RegionConfig.fromSelections(List.of(loc1, loc2));
Map<BlockKey, Double> weights = new HashMap<>();
weights.put(new BlockKey.Vanilla(Material.STONE), 100.0);
BlockConfig blocks = new BlockConfig(weights, FillMode.CUBOID, null);
MineConfig config = MineConfig.builder("TestMine_123 ") MineConfig config = MineConfig.builder("TestMine_123 ")
.region(region) .region(region)
@ -108,22 +100,15 @@ class MineConfigTest {
assertEquals("testmine_123", config.name()); assertEquals("testmine_123", config.name());
} }
}
@Test @Test
void testWorldNameDelegation() { void testWorldNameDelegation() {
try (MockedStatic<org.bukkit.Bukkit> bukkit = Mockito.mockStatic(org.bukkit.Bukkit.class)) { server.addSimpleWorld("nether");
World world = mock(World.class); World nether = server.getWorld("nether");
when(world.getName()).thenReturn("nether"); Location loc1 = new Location(nether, 0, 64, 0);
bukkit.when(() -> org.bukkit.Bukkit.getWorld("nether")).thenReturn(world); Location loc2 = new Location(nether, 10, 74, 10);
Location loc1 = new Location(world, 0, 64, 0);
Location loc2 = new Location(world, 10, 74, 10);
RegionConfig region = RegionConfig.fromSelections(List.of(loc1, loc2)); RegionConfig region = RegionConfig.fromSelections(List.of(loc1, loc2));
BlockConfig blocks = testBlocks();
Map<BlockKey, Double> weights = new HashMap<>();
weights.put(new BlockKey.Vanilla(Material.STONE), 100.0);
BlockConfig blocks = new BlockConfig(weights, FillMode.CUBOID, null);
MineConfig config = MineConfig.builder("test") MineConfig config = MineConfig.builder("test")
.region(region) .region(region)
@ -132,5 +117,4 @@ class MineConfigTest {
assertEquals("nether", config.worldName()); assertEquals("nether", config.worldName());
} }
}
} }

View file

@ -8,21 +8,47 @@ import dev.loki.lomines.data.config.region.RegionConfig;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.World; import org.bukkit.World;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic; import org.mockbukkit.mockbukkit.MockBukkit;
import org.mockito.Mockito; import org.mockbukkit.mockbukkit.ServerMock;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
class MineConfigValidationTest { class MineConfigValidationTest {
private ServerMock server;
private World world;
@BeforeEach
void setUp() {
server = MockBukkit.mock();
server.addSimpleWorld("world");
world = server.getWorld("world");
}
@AfterEach
void tearDown() {
MockBukkit.unmock();
}
private RegionConfig testRegion() {
Location loc1 = new Location(world, 0, 64, 0);
Location loc2 = new Location(world, 10, 74, 10);
return RegionConfig.fromSelections(List.of(loc1, loc2));
}
private BlockConfig testBlocks() {
Map<BlockKey, Double> weights = new HashMap<>();
weights.put(new BlockKey.Vanilla(Material.STONE), 100.0);
return new BlockConfig(weights, FillMode.CUBOID, null);
}
@Test @Test
void testNullNameThrows() { void testNullNameThrows() {
assertThrows(NullPointerException.class, () -> assertThrows(NullPointerException.class, () ->
@ -30,20 +56,9 @@ class MineConfigValidationTest {
} }
@Test @Test
@Disabled("Paper API RegistryAccess not available in unit tests")
void testBlankNameThrows() { void testBlankNameThrows() {
try (MockedStatic<org.bukkit.Bukkit> bukkit = Mockito.mockStatic(org.bukkit.Bukkit.class)) { RegionConfig region = testRegion();
World world = mock(World.class); BlockConfig blocks = testBlocks();
when(world.getName()).thenReturn("world");
bukkit.when(() -> org.bukkit.Bukkit.getWorld("world")).thenReturn(world);
Location loc1 = new Location(world, 0, 64, 0);
Location loc2 = new Location(world, 10, 74, 10);
RegionConfig region = RegionConfig.fromSelections(List.of(loc1, loc2));
Map<BlockKey, Double> weights = new HashMap<>();
weights.put(new BlockKey.Vanilla(Material.STONE), 100.0);
BlockConfig blocks = new BlockConfig(weights, FillMode.CUBOID, null);
assertThrows(IllegalArgumentException.class, () -> assertThrows(IllegalArgumentException.class, () ->
MineConfig.builder(" ") MineConfig.builder(" ")
@ -51,14 +66,10 @@ class MineConfigValidationTest {
.blocks(blocks) .blocks(blocks)
.build()); .build());
} }
}
@Test @Test
@Disabled("Paper API RegistryAccess not available in unit tests")
void testNullRegionThrows() { void testNullRegionThrows() {
Map<BlockKey, Double> weights = new HashMap<>(); BlockConfig blocks = testBlocks();
weights.put(new BlockKey.Vanilla(Material.STONE), 100.0);
BlockConfig blocks = new BlockConfig(weights, FillMode.CUBOID, null);
assertThrows(NullPointerException.class, () -> assertThrows(NullPointerException.class, () ->
MineConfig.builder("test") MineConfig.builder("test")
@ -69,14 +80,7 @@ class MineConfigValidationTest {
@Test @Test
void testNullBlocksThrows() { void testNullBlocksThrows() {
try (MockedStatic<org.bukkit.Bukkit> bukkit = Mockito.mockStatic(org.bukkit.Bukkit.class)) { RegionConfig region = testRegion();
World world = mock(World.class);
when(world.getName()).thenReturn("world");
bukkit.when(() -> org.bukkit.Bukkit.getWorld("world")).thenReturn(world);
Location loc1 = new Location(world, 0, 64, 0);
Location loc2 = new Location(world, 10, 74, 10);
RegionConfig region = RegionConfig.fromSelections(List.of(loc1, loc2));
assertThrows(NullPointerException.class, () -> assertThrows(NullPointerException.class, () ->
MineConfig.builder("test") MineConfig.builder("test")
@ -84,5 +88,4 @@ class MineConfigValidationTest {
.blocks(null) .blocks(null)
.build()); .build());
} }
}
} }

View file

@ -1,8 +1,11 @@
package dev.loki.lomines.data.config.block; package dev.loki.lomines.data.config.block;
import org.bukkit.Material; import org.bukkit.Material;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockbukkit.mockbukkit.MockBukkit;
import org.mockbukkit.mockbukkit.ServerMock;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -13,9 +16,20 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@Disabled("Paper API RegistryAccess not available in unit tests")
class BlockConfigTest { class BlockConfigTest {
private ServerMock server;
@BeforeEach
void setUp() {
server = MockBukkit.mock();
}
@AfterEach
void tearDown() {
MockBukkit.unmock();
}
@Test @Test
void testValidConfig() { void testValidConfig() {
Map<BlockKey, Double> weights = new HashMap<>(); Map<BlockKey, Double> weights = new HashMap<>();

View file

@ -1,17 +1,31 @@
package dev.loki.lomines.data.config.block; package dev.loki.lomines.data.config.block;
import org.bukkit.Material; import org.bukkit.Material;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockbukkit.mockbukkit.MockBukkit;
import org.mockbukkit.mockbukkit.ServerMock;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
@Disabled("Paper API RegistryAccess not available in unit tests")
class BlockKeyTest { class BlockKeyTest {
private ServerMock server;
@BeforeEach
void setUp() {
server = MockBukkit.mock();
}
@AfterEach
void tearDown() {
MockBukkit.unmock();
}
@Test @Test
void testVanillaSerialization() { void testVanillaSerialization() {
BlockKey.Vanilla stone = new BlockKey.Vanilla(Material.STONE); BlockKey.Vanilla stone = new BlockKey.Vanilla(Material.STONE);

View file

@ -6,7 +6,6 @@ import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
class UIConfigTest { class UIConfigTest {

View file

@ -16,7 +16,6 @@ import java.util.Collections;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -38,7 +37,7 @@ class ChunkUtilsRefreshTest {
} }
@Test @Test
void testUpdateChunks_withLargeRegion() { void testUpdateChunksWithLargeRegion() {
Location loc1 = new Location(world, 0, 0, 0); Location loc1 = new Location(world, 0, 0, 0);
Location loc2 = new Location(world, 100, 100, 100); Location loc2 = new Location(world, 100, 100, 100);
when(world.getChunkAt(anyInt(), anyInt())).thenReturn(chunk); when(world.getChunkAt(anyInt(), anyInt())).thenReturn(chunk);
@ -52,7 +51,7 @@ class ChunkUtilsRefreshTest {
} }
@Test @Test
void testUpdateChunks_withNoPlayers() { void testUpdateChunksWithNoPlayers() {
Location loc1 = new Location(world, 0, 64, 0); Location loc1 = new Location(world, 0, 64, 0);
Location loc2 = new Location(world, 5, 70, 5); Location loc2 = new Location(world, 5, 70, 5);
when(world.getChunkAt(anyInt(), anyInt())).thenReturn(chunk); when(world.getChunkAt(anyInt(), anyInt())).thenReturn(chunk);

View file

@ -18,7 +18,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -43,14 +42,14 @@ class ChunkUtilsTest {
} }
@Test @Test
void testUpdateChunks_withNullRegion_throwsException() { void testUpdateChunksWithNullRegionThrowsException() {
assertThrows(IllegalArgumentException.class, () -> { assertThrows(IllegalArgumentException.class, () -> {
ChunkUtils.updateChunks(null); ChunkUtils.updateChunks(null);
}); });
} }
@Test @Test
void testUpdateChunks_withSingleChunkRegion() { void testUpdateChunksWithSingleChunkRegion() {
// Create a small region within a single chunk // Create a small region within a single chunk
Location loc1 = new Location(world, 0, 64, 0); Location loc1 = new Location(world, 0, 64, 0);
Location loc2 = new Location(world, 5, 70, 5); Location loc2 = new Location(world, 5, 70, 5);
@ -69,7 +68,7 @@ class ChunkUtilsTest {
} }
@Test @Test
void testUpdateChunks_withMultipleChunks() { void testUpdateChunksWithMultipleChunks() {
// Create a region spanning multiple chunks (16 blocks per chunk) // Create a region spanning multiple chunks (16 blocks per chunk)
Location loc1 = new Location(world, 0, 64, 0); Location loc1 = new Location(world, 0, 64, 0);
Location loc2 = new Location(world, 32, 70, 32); Location loc2 = new Location(world, 32, 70, 32);
@ -88,7 +87,7 @@ class ChunkUtilsTest {
} }
@Test @Test
void testUpdateChunks_withNegativeCoordinates() { void testUpdateChunksWithNegativeCoordinates() {
// Test with negative coordinates // Test with negative coordinates
Location loc1 = new Location(world, -16, 64, -16); Location loc1 = new Location(world, -16, 64, -16);
Location loc2 = new Location(world, -1, 70, -1); Location loc2 = new Location(world, -1, 70, -1);

View file

@ -9,8 +9,6 @@ import org.mockito.Mockito;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
class LocationParserErrorTest { class LocationParserErrorTest {

View file

@ -4,8 +4,6 @@ import dev.loki.lomines.util.format.TimeFormatter;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
class TimeFormatterTest { class TimeFormatterTest {

View file

@ -9,7 +9,6 @@ import org.junit.jupiter.api.Test;
import java.util.List; import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;

View file

@ -26,7 +26,7 @@ class SelectionManagerTest {
} }
@Test @Test
void getSelection_createsNewSelectionIfNotExists() { void getSelectionCreatesNewSelectionIfNotExists() {
Selection selection = manager.getSelection(playerId); Selection selection = manager.getSelection(playerId);
assertNotNull(selection); assertNotNull(selection);
@ -34,7 +34,7 @@ class SelectionManagerTest {
} }
@Test @Test
void getSelection_returnsSameInstanceOnMultipleCalls() { void getSelectionReturnsSameInstanceOnMultipleCalls() {
Selection first = manager.getSelection(playerId); Selection first = manager.getSelection(playerId);
Selection second = manager.getSelection(playerId); Selection second = manager.getSelection(playerId);
@ -42,21 +42,21 @@ class SelectionManagerTest {
} }
@Test @Test
void getSelection_throwsExceptionForNullPlayerId() { void getSelectionThrowsExceptionForNullPlayerId() {
assertThrows(IllegalArgumentException.class, () -> { assertThrows(IllegalArgumentException.class, () -> {
manager.getSelection(null); manager.getSelection(null);
}); });
} }
@Test @Test
void findSelection_returnsEmptyWhenNotExists() { void findSelectionReturnsEmptyWhenNotExists() {
Optional<Selection> result = manager.findSelection(playerId); Optional<Selection> result = manager.findSelection(playerId);
assertTrue(result.isEmpty()); assertTrue(result.isEmpty());
} }
@Test @Test
void findSelection_returnsSelectionWhenExists() { void findSelectionReturnsSelectionWhenExists() {
Selection created = manager.getSelection(playerId); Selection created = manager.getSelection(playerId);
Optional<Selection> found = manager.findSelection(playerId); Optional<Selection> found = manager.findSelection(playerId);
@ -65,33 +65,33 @@ class SelectionManagerTest {
} }
@Test @Test
void findSelection_throwsExceptionForNullPlayerId() { void findSelectionThrowsExceptionForNullPlayerId() {
assertThrows(IllegalArgumentException.class, () -> { assertThrows(IllegalArgumentException.class, () -> {
manager.findSelection(null); manager.findSelection(null);
}); });
} }
@Test @Test
void hasSelection_returnsFalseWhenNotExists() { void hasSelectionReturnsFalseWhenNotExists() {
assertFalse(manager.hasSelection(playerId)); assertFalse(manager.hasSelection(playerId));
} }
@Test @Test
void hasSelection_returnsTrueWhenExists() { void hasSelectionReturnsTrueWhenExists() {
manager.getSelection(playerId); manager.getSelection(playerId);
assertTrue(manager.hasSelection(playerId)); assertTrue(manager.hasSelection(playerId));
} }
@Test @Test
void hasSelection_throwsExceptionForNullPlayerId() { void hasSelectionThrowsExceptionForNullPlayerId() {
assertThrows(IllegalArgumentException.class, () -> { assertThrows(IllegalArgumentException.class, () -> {
manager.hasSelection(null); manager.hasSelection(null);
}); });
} }
@Test @Test
void clearSelection_removesSelection() { void clearSelectionRemovesSelection() {
manager.getSelection(playerId); manager.getSelection(playerId);
assertTrue(manager.hasSelection(playerId)); assertTrue(manager.hasSelection(playerId));
@ -101,14 +101,14 @@ class SelectionManagerTest {
} }
@Test @Test
void clearSelection_doesNothingWhenSelectionNotExists() { void clearSelectionDoesNothingWhenSelectionNotExists() {
assertDoesNotThrow(() -> { assertDoesNotThrow(() -> {
manager.clearSelection(playerId); manager.clearSelection(playerId);
}); });
} }
@Test @Test
void clearSelection_throwsExceptionForNullPlayerId() { void clearSelectionThrowsExceptionForNullPlayerId() {
assertThrows(IllegalArgumentException.class, () -> { assertThrows(IllegalArgumentException.class, () -> {
manager.clearSelection(null); manager.clearSelection(null);
}); });

View file

@ -1,17 +1,14 @@
package dev.loki.lomines.util.selection; package dev.loki.lomines.util.selection;
import dev.loki.lomines.util.location.geo.Cuboid;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.World; import org.bukkit.World;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;

29
todo.md
View file

@ -1,7 +1,7 @@
# LoMines — План реструктуризации и улучшения качества # LoMines — План реструктуризации и улучшения качества
> **Обновлено:** 2026-07-06 > **Обновлено:** 2026-08-12
> **Цели:** Разбивка на подпапки (≤3 файла), лимит 150 строк/файл, Checkstyle > **Цели:** Разбивка на подпапки (≤3 файла), лимит 150 строк/файл, Checkstyle, тестовое покрытие
--- ---
@ -574,3 +574,28 @@ dev.loki.lomines/
- **0 файлов >150 строк** - **0 файлов >150 строк**
- **0 папок >3 файлов** - **0 папок >3 файлов**
- **145 main + 31 test = 176 файлов** - **145 main + 31 test = 176 файлов**
---
## ✅ Постреструктуризационные задачи завершены (2026-08-12)
### Docs
- `ARCHITECTURE.md` полностью переписан под реальный код: `dev.loki.lomines`, `extends JavaPlugin`, без lolib/LoAPI (ранее описывал Kotlin-версию)
- Удалён устаревший `core/common/package-info.java` (ссылался на `LoPlugin`, `Mines`/`Mine` в `core.common`) — пакет был пустым
### Checkstyle
- `BeforeExecutionExclusionFileFilter` (исключавший `src/test`) убран — тесты теперь проверяются
- `MethodLength` 80 → 30
- Исправлены нарушения: 23 в main (выделение private-методов) + 60 в test (unused imports, camelCase для тестов)
### Тесты: MockBukkit
- Подключён `org.mockbukkit.mockbukkit:mockbukkit-v1.21:4.110.0` (testImplementation)
- `testImplementation` paper-api приведён к `1.21.11-R0.1-SNAPSHOT` (требование MockBukkit), `compileOnly` остаётся `1.21.4`
- Включены все 48 ранее отключённых тестов: `BlockKeyTest`, `BlockConfigTest`, `MineTest`, `MineConfigTest`, `MineConfigValidationTest`, `MinesTest`, `MinesLoadingTest`, `MinesLifecycleTest`
- **Итог: 184 теста, 0 skipped, 0 failures**
### Багфикс
- `Mines.create("TestMine")` сохранял `testmine.yml` (record нормализует имя в lowercase), но загружал `TestMine.yml` → `IOException`. Исправлено нормализацией имени в `Mines.create` (`name.toLowerCase().trim()`)
### Cleanup
- Удалены `bin/` (Eclipse output, был gitignored) и `libs/` (содержал только `.gitkeep`)