From 957468b6b6c957235bef846f3bb64b0cbb3dbc91 Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Mon, 1 Jun 2026 11:12:42 +0200 Subject: [PATCH] feat: add info, teleport, and copy commands --- .../dev/loki/lomines/RegistrationManager.java | 6 + .../lomines/command/admin/CopyCommand.java | 108 ++++++++++++++++++ .../lomines/command/admin/InfoCommand.java | 105 +++++++++++++++++ .../command/common/LoMinesTabCompleter.java | 22 +++- .../command/player/TeleportCommand.java | 64 +++++++++++ todo.md | 6 +- 6 files changed, 305 insertions(+), 6 deletions(-) create mode 100644 src/main/java/dev/loki/lomines/command/admin/CopyCommand.java create mode 100644 src/main/java/dev/loki/lomines/command/admin/InfoCommand.java create mode 100644 src/main/java/dev/loki/lomines/command/player/TeleportCommand.java diff --git a/src/main/java/dev/loki/lomines/RegistrationManager.java b/src/main/java/dev/loki/lomines/RegistrationManager.java index d8e7436..5d0af9f 100644 --- a/src/main/java/dev/loki/lomines/RegistrationManager.java +++ b/src/main/java/dev/loki/lomines/RegistrationManager.java @@ -1,11 +1,14 @@ package dev.loki.lomines; import dev.loki.lomines.command.admin.AdminCommands; +import dev.loki.lomines.command.admin.CopyCommand; import dev.loki.lomines.command.admin.HologramCommands; +import dev.loki.lomines.command.admin.InfoCommand; import dev.loki.lomines.command.admin.MaskCommands; import dev.loki.lomines.command.admin.StatsCommands; import dev.loki.lomines.command.common.LoMinesTabCompleter; import dev.loki.lomines.command.player.PlayerCommands; +import dev.loki.lomines.command.player.TeleportCommand; import dev.loki.lomines.integration.IntegrationManager; import dev.loki.lomines.listener.BlockBreakListener; import dev.loki.lomines.listener.GroupGuiListener; @@ -31,6 +34,9 @@ final class RegistrationManager { commandManager.register(new StatsCommands(plugin)); commandManager.register(new MaskCommands(plugin)); commandManager.register(new HologramCommands(plugin)); + commandManager.register(new InfoCommand(plugin)); + commandManager.register(new TeleportCommand(plugin)); + commandManager.register(new CopyCommand(plugin)); plugin.loLogger().info("Commands registered"); } diff --git a/src/main/java/dev/loki/lomines/command/admin/CopyCommand.java b/src/main/java/dev/loki/lomines/command/admin/CopyCommand.java new file mode 100644 index 0000000..2051845 --- /dev/null +++ b/src/main/java/dev/loki/lomines/command/admin/CopyCommand.java @@ -0,0 +1,108 @@ +package dev.loki.lomines.command.admin; + +import dev.loki.lomines.LoMinesPlugin; +import dev.loki.lomines.core.mine.Mine; +import dev.loki.lomines.core.mine.MineConfig; +import dev.lolilb.commands.annotation.Arg; +import dev.lolilb.commands.annotation.Subcommand; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.command.CommandSender; + +import java.util.List; +import java.util.Map; + +/** + * Command to copy mine configuration from one mine to another. + */ +public class CopyCommand { + + private final LoMinesPlugin plugin; + + public CopyCommand(LoMinesPlugin plugin) { + this.plugin = plugin; + } + + /** + * Copies mine configuration (blocks, reset settings, rewards, etc.) + * from source mine to target mine. Regions and positions are NOT copied. + * Usage: /lm copy + */ + @Subcommand(value = "copy", permission = "lomines.admin.copy") + void copy(CommandSender sender, + @Arg("from-mine") String fromMineName, + @Arg("to-mine") String toMineName) { + + Mine fromMine = plugin.getMines().find(fromMineName).orElse(null); + if (fromMine == null) { + sender.sendMessage(Component.text("Исходная шахта не найдена: " + fromMineName, NamedTextColor.RED)); + return; + } + + Mine toMine = plugin.getMines().find(toMineName).orElse(null); + if (toMine == null) { + sender.sendMessage(Component.text("Целевая шахта не найдена: " + toMineName, NamedTextColor.RED)); + return; + } + + if (fromMine.getName().equalsIgnoreCase(toMine.getName())) { + sender.sendMessage(Component.text("Нельзя копировать шахту саму в себя!", NamedTextColor.RED)); + return; + } + + var sourceConfig = fromMine.getConfig(); + var targetConfig = toMine.getConfig(); + + // Copy blocks configuration + targetConfig.blocks().weights().clear(); + targetConfig.blocks().weights().putAll(sourceConfig.blocks().weights()); + + // Copy reset settings + var targetReset = targetConfig.reset(); + var sourceReset = sourceConfig.reset(); + targetReset.intervalSeconds(sourceReset.intervalSeconds()); + targetReset.percentTrigger(sourceReset.percentTrigger()); + targetReset.percentTriggerEnabled(sourceReset.isPercentTriggerEnabled()); + targetReset.intervalDisplay(sourceReset.intervalDisplay()); + + // Copy teleport and spawn settings + var targetTeleport = targetConfig.teleport(); + var sourceTeleport = sourceConfig.teleport(); + targetTeleport.enabled(sourceTeleport.enabled()); + if (sourceTeleport.getLocation().isPresent()) { + targetTeleport.setLocation(sourceTeleport.getLocation().get()); + } else { + targetTeleport.setLocation(null); + } + + var targetSpawn = targetConfig.playerSpawn(); + var sourceSpawn = sourceConfig.playerSpawn(); + targetSpawn.enabled(sourceSpawn.enabled()); + if (sourceSpawn.getLocation().isPresent()) { + targetSpawn.setLocation(sourceSpawn.getLocation().get()); + } else { + targetSpawn.setLocation(null); + } + + // Copy rewards + targetConfig.rewards().entries().clear(); + targetConfig.rewards().entries().addAll(sourceConfig.rewards().entries()); + + // Copy other settings + targetConfig.blockPhysics(sourceConfig.isBlockPhysicsEnabled()); + targetConfig.entitySpawning(sourceConfig.isEntitySpawningEnabled()); + targetConfig.liquidFlow(sourceConfig.isLiquidFlowEnabled()); + + // Save target mine + toMine.save(); + + sender.sendMessage(Component.text("§aКонфигурация скопирована из §f" + fromMine.getName() + + " §aв §f" + toMine.getName())); + sender.sendMessage(Component.text("§7Скопировано:")); + sender.sendMessage(Component.text(" §7• Блоки: §f" + sourceConfig.blocks().weights().size() + " типов")); + sender.sendMessage(Component.text(" §7• Настройки сброса")); + sender.sendMessage(Component.text(" §7• Точки телепорта/спавна")); + sender.sendMessage(Component.text(" §7• Награды: §f" + sourceConfig.rewards().entries().size() + " записей")); + sender.sendMessage(Component.text("§7§oПримечание: регионы и позиции не скопированы")); + } +} diff --git a/src/main/java/dev/loki/lomines/command/admin/InfoCommand.java b/src/main/java/dev/loki/lomines/command/admin/InfoCommand.java new file mode 100644 index 0000000..2108c13 --- /dev/null +++ b/src/main/java/dev/loki/lomines/command/admin/InfoCommand.java @@ -0,0 +1,105 @@ +package dev.loki.lomines.command.admin; + +import dev.loki.lomines.LoMinesPlugin; +import dev.loki.lomines.core.mine.Mine; +import dev.loki.lomines.data.config.block.BlockKey; +import dev.loki.lomines.util.location.Cuboid; +import dev.lolilb.commands.annotation.Arg; +import dev.lolilb.commands.annotation.Subcommand; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; +import java.util.Map; + +/** + * Command to display detailed information about a mine. + */ +public class InfoCommand { + + private final LoMinesPlugin plugin; + + public InfoCommand(LoMinesPlugin plugin) { + this.plugin = plugin; + } + + /** + * Shows detailed information about a mine. + * Usage: /lm info + */ + @Subcommand(value = "info", permission = "lomines.admin.info") + void info(CommandSender sender, @Arg("mine") String mineName) { + Mine mine = plugin.getMines().find(mineName).orElse(null); + if (mine == null) { + sender.sendMessage(Component.text("Шахта не найдена: " + mineName, NamedTextColor.RED)); + return; + } + + var config = mine.getConfig(); + + sender.sendMessage(Component.text("§8§m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")); + sender.sendMessage(Component.text("§6§lИнформация о шахте: §f" + mine.getName())); + sender.sendMessage(Component.text("")); + + // Basic info + sender.sendMessage(Component.text("§7Мир: §f" + config.worldName())); + sender.sendMessage(Component.text("§7Блоков: §f" + mine.getBlocks() + "§7/§f" + mine.getTotalVolume() + + " §7(§f" + String.format("%.1f", mine.getPercentFilled()) + "%§7)")); + sender.sendMessage(Component.text("")); + + // Regions + sender.sendMessage(Component.text("§6§lРегионы:")); + List regions = mine.getRegions(); + for (int i = 0; i < regions.size(); i++) { + Cuboid r = regions.get(i); + sender.sendMessage(Component.text("§7 #" + (i + 1) + ": §f" + + r.getMinX() + "," + r.getMinY() + "," + r.getMinZ() + " §7-> §f" + + r.getMaxX() + "," + r.getMaxY() + "," + r.getMaxZ() + + " §7(§f" + r.getVolume() + " §7блоков)")); + } + sender.sendMessage(Component.text("")); + + // Blocks + sender.sendMessage(Component.text("§6§lБлоки:")); + Map weights = config.blocks().weights(); + weights.entrySet().stream() + .sorted((a, b) -> Double.compare(b.getValue(), a.getValue())) + .limit(10) + .forEach(e -> sender.sendMessage(Component.text( + "§7 " + formatBlockKey(e.getKey()) + ": §f" + String.format("%.1f", e.getValue() * 100) + "%"))); + if (weights.size() > 10) { + sender.sendMessage(Component.text("§7 ... и ещё " + (weights.size() - 10) + " блоков")); + } + sender.sendMessage(Component.text("")); + + // Reset settings + sender.sendMessage(Component.text("§6§lНастройки сброса:")); + sender.sendMessage(Component.text("§7 Интервал: §f" + config.reset().intervalDisplay())); + sender.sendMessage(Component.text("§7 Триггер по %: §f" + + (config.reset().isPercentTriggerEnabled() ? config.reset().percentTrigger() + "%" : "выкл"))); + sender.sendMessage(Component.text("")); + + // Teleport/Spawn + sender.sendMessage(Component.text("§6§lТелепортация:")); + sender.sendMessage(Component.text("§7 Точка телепорта: §f" + + (config.teleport().enabled() ? "§aустановлена" : "§7не задана"))); + sender.sendMessage(Component.text("§7 Точка спавна: §f" + + (config.playerSpawn().enabled() ? "§aустановлена" : "§7используется точка телепорта"))); + sender.sendMessage(Component.text("")); + + // Rewards + sender.sendMessage(Component.text("§6§lНаграды: §f" + config.rewards().entries().size() + " записей")); + + sender.sendMessage(Component.text("§8§m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")); + } + + private String formatBlockKey(BlockKey key) { + return switch (key) { + case BlockKey.Vanilla v -> v.material().name().toLowerCase(); + case BlockKey.Oraxen o -> "oraxen:" + o.id(); + case BlockKey.ItemsAdder i -> "ia:" + i.id(); + }; + } +} diff --git a/src/main/java/dev/loki/lomines/command/common/LoMinesTabCompleter.java b/src/main/java/dev/loki/lomines/command/common/LoMinesTabCompleter.java index 0ac51f4..b90ef64 100644 --- a/src/main/java/dev/loki/lomines/command/common/LoMinesTabCompleter.java +++ b/src/main/java/dev/loki/lomines/command/common/LoMinesTabCompleter.java @@ -21,7 +21,7 @@ public class LoMinesTabCompleter implements TabCompleter { private static final List ALL_COMMANDS = List.of( "create", "delete", "reset", "reload", "list", "wand", "group", "stats", "top", "maskscan", "edit", "setteleport", "setspawn", - "clearspawn", "help" + "clearspawn", "info", "tp", "copy", "help" ); private static final List BOOLEAN_VALUES = List.of("true", "false"); @@ -67,11 +67,13 @@ public class LoMinesTabCompleter implements TabCompleter { private List completeSecondArg(CommandSender sender, String subcommand, String partial) { return switch (subcommand) { case "create" -> completeCreate(partial); - case "delete", "maskscan", "edit", "setteleport", "setspawn", "clearspawn" -> + case "delete", "maskscan", "edit", "setteleport", "setspawn", "clearspawn", "info" -> filterStartsWith(getMineNames(), partial); case "reset" -> completeReset(partial); case "stats" -> completeStats(sender, partial); case "top" -> completeTopFirstArg(partial); + case "tp" -> completeTeleport(sender, partial); + case "copy" -> completeCopyFirstArg(partial); default -> new ArrayList<>(); }; } @@ -80,6 +82,7 @@ public class LoMinesTabCompleter implements TabCompleter { return switch (subcommand) { case "reset" -> filterStartsWith(BOOLEAN_VALUES, partial); case "top" -> completeTopSecondArg(arg2, partial); + case "copy" -> filterStartsWith(getMineNames(), partial); default -> new ArrayList<>(); }; } @@ -132,6 +135,17 @@ public class LoMinesTabCompleter implements TabCompleter { return new ArrayList<>(); } + private List completeTeleport(CommandSender sender, String partial) { + if (!sender.hasPermission("lomines.teleport")) { + return new ArrayList<>(); + } + return filterStartsWith(getMineNames(), partial); + } + + private List completeCopyFirstArg(String partial) { + return filterStartsWith(getMineNames(), partial); + } + private boolean isNumericPartial(String partial) { return partial.isEmpty() || partial.matches("\\d*"); } @@ -156,13 +170,15 @@ public class LoMinesTabCompleter implements TabCompleter { private boolean hasPermission(CommandSender sender, String cmd) { return switch (cmd) { - case "create", "delete", "reload", "list", "maskscan", "edit" -> + case "create", "delete", "reload", "list", "maskscan", "edit", "info" -> sender.hasPermission("lomines.admin"); case "reset" -> sender.hasPermission("lomines.admin.reset"); case "setteleport" -> sender.hasPermission("lomines.admin.setteleport"); case "setspawn", "clearspawn" -> sender.hasPermission("lomines.admin.setspawn"); case "wand", "group" -> sender.hasPermission("lomines.admin.wand"); case "stats", "top" -> sender.hasPermission("lomines.stats"); + case "tp" -> sender.hasPermission("lomines.teleport"); + case "copy" -> sender.hasPermission("lomines.admin.copy"); case "help" -> sender.hasPermission("lomines.use"); default -> true; }; diff --git a/src/main/java/dev/loki/lomines/command/player/TeleportCommand.java b/src/main/java/dev/loki/lomines/command/player/TeleportCommand.java new file mode 100644 index 0000000..a768bf3 --- /dev/null +++ b/src/main/java/dev/loki/lomines/command/player/TeleportCommand.java @@ -0,0 +1,64 @@ +package dev.loki.lomines.command.player; + +import dev.loki.lomines.LoMinesPlugin; +import dev.loki.lomines.core.mine.Mine; +import dev.loki.lomines.util.location.Cuboid; +import dev.lolilb.commands.annotation.Arg; +import dev.lolilb.commands.annotation.Subcommand; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Location; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +/** + * Command to teleport to a mine. + */ +public class TeleportCommand { + + private final LoMinesPlugin plugin; + + public TeleportCommand(LoMinesPlugin plugin) { + this.plugin = plugin; + } + + /** + * Teleports player to the mine's location. + * Usage: /lm tp + */ + @Subcommand(value = "tp", permission = "lomines.teleport") + void teleport(CommandSender sender, @Arg("mine") String mineName) { + if (!(sender instanceof Player player)) { + sender.sendMessage(Component.text("Эта команда только для игроков!", NamedTextColor.RED)); + return; + } + + Mine mine = plugin.getMines().find(mineName).orElse(null); + if (mine == null) { + player.sendMessage(Component.text("Шахта не найдена: " + mineName, NamedTextColor.RED)); + return; + } + + var config = mine.getConfig(); + Location target; + + // Try teleport location first + if (config.teleport().enabled() && config.teleport().getLocation().isPresent()) { + target = config.teleport().getLocation().get(); + } else if (!mine.getRegions().isEmpty()) { + // Use center of first region + 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; + } + + player.teleport(target); + player.sendMessage(Component.text("§aТелепортировано к шахте §f" + mine.getName() + + " §a(§f" + String.format("%.0f, %.0f, %.0f", target.getX(), target.getY(), target.getZ()) + "§a)")); + } +} diff --git a/todo.md b/todo.md index 3bc1880..267df22 100644 --- a/todo.md +++ b/todo.md @@ -446,9 +446,9 @@ dev.loki.lomines/ - [x] PlaceholderAPI интеграция ✅ ### 🟢 Низкий -- [ ] Добавить `/lm info ` — детальная информация -- [ ] Добавить `/lm tp ` — телепортация -- [ ] Добавить `/lm copy ` — копирование +- [x] Добавить `/lm info ` — детальная информация ✅ +- [x] Добавить `/lm tp ` — телепортация ✅ +- [x] Добавить `/lm copy ` — копирование ✅ - [x] Разбить большие GUI файлы (>200 строк) ✅ ---